web: convert everything to TypeScript

This commit is contained in:
Maximilian Hils 2021-08-23 11:01:43 +02:00
parent c5e3e3d636
commit 3589ec2f58
69 changed files with 679 additions and 730 deletions

View File

@ -1,18 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import FilterDocs from '../../../components/Header/FilterDocs'
describe('FilterDocs Component', () => {
let mockResponse = { json:
() => { return { commands: [['cmd1', 'foo'], ['cmd2', 'bar']]}}
},
promise = Promise.resolve(mockResponse)
global.fetch = r => { return promise }
let filterDocs = renderer.create(<FilterDocs/>),
tree = filterDocs.toJSON()
it('should render correctly when fetch success', () => {
expect(tree).toMatchSnapshot()
})
})

View File

@ -0,0 +1,20 @@
import * as React from "react"
import FilterDocs from '../../../components/Header/FilterDocs'
import {enableFetchMocks} from "jest-fetch-mock";
import {render, screen, waitFor} from "../../test-utils";
enableFetchMocks();
test("FilterDocs Component", async () => {
fetchMock.mockOnceIf("./filter-help", JSON.stringify({
commands: [['cmd1', 'foo'], ['cmd2', 'bar']]
}))
const {asFragment} = render(<FilterDocs selectHandler={() => 0}/>);
expect(asFragment()).toMatchSnapshot();
await waitFor(() => screen.getByText("cmd1"));
expect(asFragment()).toMatchSnapshot();
})

View File

@ -4,11 +4,12 @@ import FilterInput from '../../../components/Header/FilterInput'
import FilterDocs from '../../../components/Header/FilterDocs'
import TestUtil from 'react-dom/test-utils'
import ReactDOM from 'react-dom'
import { Key } from '../../../utils'
describe('FilterInput Component', () => {
it('should render correctly', () => {
let filterInput = renderer.create(<FilterInput type='foo' color='red' placeholder='bar'/>),
let filterInput = renderer.create(
<FilterInput type='foo' color='red' placeholder='bar' onChange={() => undefined} value="42"/>
),
tree = filterInput.toJSON()
expect(tree).toMatchSnapshot()
})
@ -69,7 +70,7 @@ describe('FilterInput Component', () => {
it('should handle keyDown', () => {
input.blur = jest.fn()
let mockEvent = {
keyCode: Key.ESC,
key: "Escape",
stopPropagation: jest.fn()
}
filterInput.onKeyDown(mockEvent)

View File

@ -1,7 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FilterDocs Component should render correctly when fetch success 1`] = `
<i
className="fa fa-spinner fa-spin"
/>
`;

View File

@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FilterDocs Component 1`] = `
<DocumentFragment>
<i
class="fa fa-spinner fa-spin"
/>
</DocumentFragment>
`;
exports[`FilterDocs Component 2`] = `
<DocumentFragment>
<table
class="table table-condensed"
>
<tbody>
<tr>
<td>
cmd1
</td>
<td>
foo
</td>
</tr>
<tr>
<td>
cmd2
</td>
<td>
bar
</td>
</tr>
<tr>
<td
colspan="2"
>
<a
href="https://mitmproxy.org/docs/latest/concepts-filters/"
target="_blank"
>
<i
class="fa fa-external-link"
/>
  mitmproxy docs
</a>
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;

View File

@ -24,6 +24,7 @@ exports[`FilterInput Component should render correctly 1`] = `
onKeyDown={[Function]}
placeholder="bar"
type="text"
value="42"
/>
</div>
`;

View File

@ -16,7 +16,7 @@ describe('Button Component', () => {
it('should be able to be disabled', () => {
let button = renderer.create(
<Button className="classname" onClick={() => "onclick"} disabled="true" children="children">
<Button className="classname" onClick={() => "onclick"} disabled>
<a>foo</a>
</Button>
),

View File

@ -7,12 +7,12 @@ import TestUtils from 'react-dom/test-utils';
describe('Splitter Component', () => {
it('should render correctly', () => {
let splitter = renderer.create(<Splitter></Splitter>),
let splitter = renderer.create(<Splitter/>),
tree = splitter.toJSON()
expect(tree).toMatchSnapshot()
})
let splitter = TestUtils.renderIntoDocument(<Splitter></Splitter>),
let splitter = TestUtils.renderIntoDocument(<Splitter/>),
dom = ReactDOM.findDOMNode(splitter),
previousElementSibling = {
offsetHeight: 0,
@ -25,7 +25,7 @@ describe('Splitter Component', () => {
it('should handle mouseDown ', () => {
window.addEventListener = jest.fn()
splitter.onMouseDown({ pageX: 1, pageY: 2})
splitter.onMouseDown({pageX: 1, pageY: 2})
expect(splitter.state.startX).toEqual(1)
expect(splitter.state.startY).toEqual(2)
expect(window.addEventListener).toBeCalledWith('mousemove', splitter.onMouseMove)
@ -44,9 +44,9 @@ describe('Splitter Component', () => {
it('should handle mouseUp', () => {
Object.defineProperty(dom, 'previousElementSibling', { value: previousElementSibling })
Object.defineProperty(dom, 'nextElementSibling', { value: nextElementSibling })
splitter.onMouseUp({ pageX: 3, pageY: 4 })
Object.defineProperty(dom, 'previousElementSibling', {value: previousElementSibling})
Object.defineProperty(dom, 'nextElementSibling', {value: nextElementSibling})
splitter.onMouseUp({pageX: 3, pageY: 4})
expect(splitter.state.applied).toBeTruthy()
expect(nextElementSibling.style.flex).toEqual('1 1 auto')
expect(previousElementSibling.style.flex).toEqual('0 0 2px')
@ -56,15 +56,15 @@ describe('Splitter Component', () => {
splitter.onMouseMove({pageX: 10, pageY: 10})
expect(dom.style.transform).toEqual("translate(9px, 0px)")
let splitterY = TestUtils.renderIntoDocument(<Splitter axis="y"></Splitter>)
let splitterY = TestUtils.renderIntoDocument(<Splitter axis="y"/>)
splitterY.onMouseMove({pageX: 10, pageY: 10})
expect(ReactDOM.findDOMNode(splitterY).style.transform).toEqual("translate(0px, 10px)")
})
it('should handle resize', () => {
window.setTimeout = jest.fn((event, time) => event())
let x = jest.spyOn(window, 'setTimeout');
splitter.onResize()
expect(window.setTimeout).toHaveBeenCalled()
expect(x).toHaveBeenCalled()
})
it('should handle componentWillUnmount', () => {

View File

@ -7,18 +7,14 @@ describe('ToggleButton Component', () => {
it('should render correctly', () => {
let checkedButton = renderer.create(
<ToggleButton checked={true} onToggle={mockFunc} text="foo">
text
</ToggleButton>),
<ToggleButton checked={true} onToggle={mockFunc} text="foo"/>),
tree = checkedButton.toJSON()
expect(tree).toMatchSnapshot()
})
it('should handle click action', () => {
let uncheckButton = renderer.create(
<ToggleButton checked={false} onToggle={mockFunc} text="foo">
text
</ToggleButton>),
<ToggleButton checked={false} onToggle={mockFunc} text="foo"/>),
tree = uncheckButton.toJSON()
tree.props.onClick()
expect(mockFunc).toBeCalled()

View File

@ -1,43 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import ToggleInputButton from '../../../components/common/ToggleInputButton'
import { Key } from '../../../utils'
describe('ToggleInputButton Component', () => {
let mockFunc = jest.fn(),
toggleInputButton = undefined,
tree = undefined
it('should render correctly', () => {
toggleInputButton = renderer.create(
<ToggleInputButton checked={true} name="foo" onToggleChanged={mockFunc}
placeholder="bar">text</ToggleInputButton>)
tree = toggleInputButton.toJSON()
expect(tree).toMatchSnapshot()
})
it('should handle keydown and click action', () => {
toggleInputButton = renderer.create(
<ToggleInputButton checked={false} name="foo" onToggleChanged={mockFunc}
placeholder="bar" txt="txt">text</ToggleInputButton>)
tree = toggleInputButton.toJSON()
let mockEvent = {
keyCode: Key.ENTER,
stopPropagation: jest.fn()
}
tree.children[1].props.onKeyDown(mockEvent)
expect(mockFunc).toBeCalledWith("txt")
tree.children[0].props.onClick()
expect(mockFunc).toBeCalledWith("txt")
})
it('should update state onChange', () => {
// trigger onChange
tree.children[1].props.onChange({ target: { value: "foo" }})
// update the tree
tree = toggleInputButton.toJSON()
expect(tree.children[1].props.value).toEqual("foo")
})
})

View File

@ -3,7 +3,7 @@
exports[`Button Component should be able to be disabled 1`] = `
<button
className="classname btn btn-default"
disabled="true"
disabled={true}
>
<a>
foo

View File

@ -1,31 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ToggleInputButton Component should render correctly 1`] = `
<div
className="input-group toggle-input-btn"
>
<span
className="input-group-btn"
onClick={[Function]}
>
<div
className="btn btn-primary"
>
<span
className="fa fa-check-square-o"
/>
 
foo
</div>
</span>
<input
className="form-control"
disabled={true}
onChange={[Function]}
onKeyDown={[Function]}
placeholder="bar"
type="text"
value=""
/>
</div>
`;

View File

@ -14,7 +14,7 @@ describe('VirtualScroll', () => {
it('should calculate position with itemHeights', () => {
expect(calcVScroll({itemCount: 5, itemHeights: [100, 100, 100, 100, 100],
viewportHeight: 300, viewportTop: 0})).toEqual({
viewportHeight: 300, viewportTop: 0, rowHeight: 100})).toEqual({
start: 0, end: 4, paddingTop: 0, paddingBottom: 100
})
})

View File

@ -4,7 +4,7 @@ import { ConnectionState } from "../../ducks/connection"
describe('connection reducer', () => {
it('should return initial state', () => {
expect(reduceConnection(undefined, {})).toEqual({
expect(reduceConnection(undefined, {type: "other"})).toEqual({
state: ConnectionState.INIT,
message: undefined,
})

View File

@ -2,7 +2,7 @@ import {rootReducer} from '../../ducks/index'
describe('reduceState in js/ducks/index.js', () => {
it('should combine flow and header', () => {
let state = rootReducer(undefined, {})
let state = rootReducer(undefined, {type: "other"})
expect(state.hasOwnProperty('eventLog')).toBeTruthy()
expect(state.hasOwnProperty('flows')).toBeTruthy()
expect(state.hasOwnProperty('connection')).toBeTruthy()

View File

@ -2,7 +2,7 @@ import reduceUI from '../../../ducks/ui/index'
describe('reduceUI in js/ducks/ui/index.js', () => {
it('should combine flow and header', () => {
let state = reduceUI(undefined, {})
let state = reduceUI(undefined, {type: "other"})
expect(state.hasOwnProperty('flow')).toBeTruthy()
})
})

View File

@ -1,164 +0,0 @@
jest.mock('../../../utils')
import { Key } from '../../../utils'
import { onKeyDown } from '../../../ducks/ui/keyboard'
import reduceFlows from '../../../ducks/flows'
import reduceUI from '../../../ducks/ui/index'
import * as flowsActions from '../../../ducks/flows'
import * as UIActions from '../../../ducks/ui/flow'
import * as modalActions from '../../../ducks/ui/modal'
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { fetchApi } from '../../../utils'
const mockStore = configureStore([ thunk ])
console.debug = jest.fn()
describe('onKeyDown', () => {
let flows = undefined
for( let i=1; i <= 12; i++ ) {
flows = reduceFlows(flows, {type: flowsActions.ADD, data: {id: i, request: true, response: true}, cmd: 'add'})
}
let store = mockStore({ flows, ui: reduceUI(undefined, {}) })
let createKeyEvent = (keyCode, shiftKey = undefined, ctrlKey = undefined) => {
return onKeyDown({ keyCode, shiftKey, ctrlKey, preventDefault: jest.fn() })
}
afterEach(() => {
store.clearActions()
fetchApi.mockClear()
});
it('should handle cursor up', () => {
store.getState().flows = reduceFlows(flows, flowsActions.select(2))
store.dispatch(createKeyEvent(Key.K))
expect(store.getActions()).toEqual([{ flowIds: [1], type: flowsActions.SELECT }])
store.clearActions()
store.dispatch(createKeyEvent(Key.UP))
expect(store.getActions()).toEqual([{ flowIds: [1], type: flowsActions.SELECT }])
})
it('should handle cursor down', () => {
store.dispatch(createKeyEvent(Key.J))
expect(store.getActions()).toEqual([{ flowIds: [3], type: flowsActions.SELECT }])
store.clearActions()
store.dispatch(createKeyEvent(Key.DOWN))
expect(store.getActions()).toEqual([{ flowIds: [3], type: flowsActions.SELECT }])
})
it('should handle page down', () => {
store.dispatch(createKeyEvent(Key.SPACE))
expect(store.getActions()).toEqual([{ flowIds: [12], type: flowsActions.SELECT }])
store.getState().flows = reduceFlows(flows, flowsActions.select(1))
store.clearActions()
store.dispatch(createKeyEvent(Key.PAGE_DOWN))
expect(store.getActions()).toEqual([{ flowIds: [11], type: flowsActions.SELECT }])
})
it('should handle page up', () => {
store.getState().flows = reduceFlows(flows, flowsActions.select(11))
store.dispatch(createKeyEvent(Key.PAGE_UP))
expect(store.getActions()).toEqual([{ flowIds: [1], type: flowsActions.SELECT }])
})
it('should handle select first', () => {
store.dispatch(createKeyEvent(Key.HOME))
expect(store.getActions()).toEqual([{ flowIds: [1], type: flowsActions.SELECT }])
})
it('should handle select last', () => {
store.getState().flows = reduceFlows(flows, flowsActions.select(1))
store.dispatch(createKeyEvent(Key.END))
expect(store.getActions()).toEqual([{ flowIds: [12], type: flowsActions.SELECT }])
})
it('should handle deselect', () => {
store.dispatch(createKeyEvent(Key.ESC))
expect(store.getActions()).toEqual([{ flowIds: [], type: flowsActions.SELECT }])
})
it('should handle switch to left tab', () => {
store.dispatch(createKeyEvent(Key.LEFT))
expect(store.getActions()).toEqual([{ tab: 'timing', type: UIActions.SET_TAB }])
})
it('should handle switch to right tab', () => {
store.dispatch(createKeyEvent(Key.TAB))
expect(store.getActions()).toEqual([{ tab: 'response', type: UIActions.SET_TAB }])
store.clearActions()
store.dispatch(createKeyEvent(Key.RIGHT))
expect(store.getActions()).toEqual([{ tab: 'response', type: UIActions.SET_TAB }])
})
it('should handle delete action', () => {
store.dispatch(createKeyEvent(Key.D))
expect(fetchApi).toBeCalledWith('/flows/1', { method: 'DELETE' })
})
it('should handle duplicate action', () => {
store.dispatch(createKeyEvent(Key.D, true))
expect(fetchApi).toBeCalledWith('/flows/1/duplicate', { method: 'POST' })
})
it('should handle resume action', () => {
// resume all
store.dispatch(createKeyEvent(Key.A, true))
expect(fetchApi).toBeCalledWith('/flows/resume', { method: 'POST' })
// resume
store.getState().flows.byId[store.getState().flows.selected[0]].intercepted = true
store.dispatch(createKeyEvent(Key.A))
expect(fetchApi).toBeCalledWith('/flows/1/resume', { method: 'POST' })
})
it('should handle replay action', () => {
store.dispatch(createKeyEvent(Key.R))
expect(fetchApi).toBeCalledWith('/flows/1/replay', { method: 'POST' })
})
it('should handle revert action', () => {
store.getState().flows.byId[store.getState().flows.selected[0]].modified = true
store.dispatch(createKeyEvent(Key.V))
expect(fetchApi).toBeCalledWith('/flows/1/revert', { method: 'POST' })
})
it('should handle kill action', () => {
// kill all
store.dispatch(createKeyEvent(Key.X, true))
expect(fetchApi).toBeCalledWith('/flows/kill', { method: 'POST' })
// kill
store.dispatch(createKeyEvent(Key.X))
expect(fetchApi).toBeCalledWith('/flows/1/kill', { method: 'POST' })
})
it('should handle clear action', () => {
store.dispatch(createKeyEvent(Key.Z))
expect(fetchApi).toBeCalledWith('/clear', { method: 'POST' })
})
it('should stop on some action with no flow is selected', () => {
store.getState().flows = reduceFlows(undefined, {})
store.dispatch(createKeyEvent(Key.LEFT))
store.dispatch(createKeyEvent(Key.TAB))
store.dispatch(createKeyEvent(Key.RIGHT))
store.dispatch(createKeyEvent(Key.D))
expect(fetchApi).not.toBeCalled()
})
it('should do nothing when Ctrl and undefined key is pressed ', () => {
store.dispatch(createKeyEvent(Key.BACKSPACE, false, true))
store.dispatch(createKeyEvent(0))
expect(fetchApi).not.toBeCalled()
})
it('should close modal', () => {
store.getState().ui.modal.activeModal = true
store.dispatch(createKeyEvent(Key.ESC))
expect(store.getActions()).toEqual([ {type: modalActions.HIDE_MODAL} ])
})
})

View File

@ -0,0 +1,166 @@
import reduceFlows, * as flowsActions from "../../../ducks/flows";
import {onKeyDown} from '../../../ducks/ui/keyboard'
import * as UIActions from '../../../ducks/ui/flow'
import * as modalActions from '../../../ducks/ui/modal'
import {fetchApi} from '../../../utils'
import {TStore} from "../tutils";
jest.mock('../../../utils')
describe('onKeyDown', () => {
let flows = flowsActions.defaultState;
for (let i = 1; i <= 12; i++) {
flows = reduceFlows(flows, {
type: flowsActions.ADD,
data: {id: i + "", request: true, response: true},
cmd: 'add'
})
}
const store = TStore();
store.getState().flows = flows;
let createKeyEvent = (key, ctrlKey = false) => {
// @ts-ignore
return onKeyDown({key, ctrlKey, preventDefault: jest.fn()})
}
afterEach(() => {
store.clearActions()
// @ts-ignore
fetchApi.mockClear()
});
it('should handle cursor up', () => {
store.getState().flows = reduceFlows(flows, flowsActions.select("2"))
store.dispatch(createKeyEvent("k"))
expect(store.getActions()).toEqual([{flowIds: ["1"], type: flowsActions.SELECT}])
store.clearActions()
store.dispatch(createKeyEvent("ArrowUp"))
expect(store.getActions()).toEqual([{flowIds: ["1"], type: flowsActions.SELECT}])
})
it('should handle cursor down', () => {
store.dispatch(createKeyEvent("j"))
expect(store.getActions()).toEqual([{flowIds: ["3"], type: flowsActions.SELECT}])
store.clearActions()
store.dispatch(createKeyEvent("ArrowDown"))
expect(store.getActions()).toEqual([{flowIds: ["3"], type: flowsActions.SELECT}])
})
it('should handle page down', () => {
store.dispatch(createKeyEvent(" "))
expect(store.getActions()).toEqual([{flowIds: ["12"], type: flowsActions.SELECT}])
store.getState().flows = reduceFlows(flows, flowsActions.select("1"))
store.clearActions()
store.dispatch(createKeyEvent("PageDown"))
expect(store.getActions()).toEqual([{flowIds: ["11"], type: flowsActions.SELECT}])
})
it('should handle page up', () => {
store.getState().flows = reduceFlows(flows, flowsActions.select("11"))
store.dispatch(createKeyEvent("PageUp"))
expect(store.getActions()).toEqual([{flowIds: ["1"], type: flowsActions.SELECT}])
})
it('should handle select first', () => {
store.dispatch(createKeyEvent("Home"))
expect(store.getActions()).toEqual([{flowIds: ["1"], type: flowsActions.SELECT}])
})
it('should handle select last', () => {
store.getState().flows = reduceFlows(flows, flowsActions.select("1"))
store.dispatch(createKeyEvent("End"))
expect(store.getActions()).toEqual([{flowIds: ["12"], type: flowsActions.SELECT}])
})
it('should handle deselect', () => {
store.dispatch(createKeyEvent("Escape"))
expect(store.getActions()).toEqual([{flowIds: [], type: flowsActions.SELECT}])
})
it('should handle switch to left tab', () => {
store.dispatch(createKeyEvent("ArrowLeft"))
expect(store.getActions()).toEqual([{tab: 'timing', type: UIActions.SET_TAB}])
})
it('should handle switch to right tab', () => {
store.dispatch(createKeyEvent("Tab"))
expect(store.getActions()).toEqual([{tab: 'response', type: UIActions.SET_TAB}])
store.clearActions()
store.dispatch(createKeyEvent("ArrowRight"))
expect(store.getActions()).toEqual([{tab: 'response', type: UIActions.SET_TAB}])
})
it('should handle delete action', () => {
store.dispatch(createKeyEvent("d"))
expect(fetchApi).toBeCalledWith('/flows/1', {method: 'DELETE'})
})
it('should handle duplicate action', () => {
store.dispatch(createKeyEvent("D"))
expect(fetchApi).toBeCalledWith('/flows/1/duplicate', {method: 'POST'})
})
it('should handle resume action', () => {
// resume all
store.dispatch(createKeyEvent("A"))
expect(fetchApi).toBeCalledWith('/flows/resume', {method: 'POST'})
// resume
store.getState().flows.byId[store.getState().flows.selected[0]].intercepted = true
store.dispatch(createKeyEvent("a"))
expect(fetchApi).toBeCalledWith('/flows/1/resume', {method: 'POST'})
})
it('should handle replay action', () => {
store.dispatch(createKeyEvent("r"))
expect(fetchApi).toBeCalledWith('/flows/1/replay', {method: 'POST'})
})
it('should handle revert action', () => {
store.getState().flows.byId[store.getState().flows.selected[0]].modified = true
store.dispatch(createKeyEvent("v"))
expect(fetchApi).toBeCalledWith('/flows/1/revert', {method: 'POST'})
})
it('should handle kill action', () => {
// kill all
store.dispatch(createKeyEvent("X"))
expect(fetchApi).toBeCalledWith('/flows/kill', {method: 'POST'})
// kill
store.dispatch(createKeyEvent("x"))
expect(fetchApi).toBeCalledWith('/flows/1/kill', {method: 'POST'})
})
it('should handle clear action', () => {
store.dispatch(createKeyEvent("z"))
expect(fetchApi).toBeCalledWith('/clear', {method: 'POST'})
})
it('should stop on some action with no flow is selected', () => {
store.getState().flows = reduceFlows(undefined, {})
store.dispatch(createKeyEvent("ArrowLeft"))
store.dispatch(createKeyEvent("Tab"))
store.dispatch(createKeyEvent("ArrowRight"))
store.dispatch(createKeyEvent("D"))
expect(fetchApi).not.toBeCalled()
})
it('should do nothing when Ctrl and undefined key is pressed ', () => {
store.dispatch(createKeyEvent("Backspace", true))
store.dispatch(createKeyEvent(0))
expect(fetchApi).not.toBeCalled()
})
it('should close modal', () => {
store.getState().ui.modal.activeModal = true
store.dispatch(createKeyEvent("Escape"))
expect(store.getActions()).toEqual([{type: modalActions.HIDE_MODAL}])
})
})

View File

@ -1,7 +1,6 @@
import reduceModal, * as ModalActions from '../../../ducks/ui/modal'
describe('modal reducer', () => {
let state = undefined
it('should return the initial state', () => {
expect(reduceModal(undefined, {})).toEqual(
@ -10,14 +9,14 @@ describe('modal reducer', () => {
})
it('should handle setActiveModal action', () => {
state = reduceModal(undefined, ModalActions.setActiveModal('foo'))
let state = reduceModal(undefined, ModalActions.setActiveModal('foo'))
expect(state).toEqual(
{ activeModal: 'foo' }
)
})
it('should handle hideModal action', () => {
state = reduceModal(state, ModalActions.hideModal())
let state = reduceModal(undefined, ModalActions.hideModal())
expect(state).toEqual(
{ activeModal: undefined }
)

View File

@ -1,5 +1,6 @@
import reduceOptionsEditor, * as optionsEditorActions from '../../../ducks/ui/optionsEditor'
import { HIDE_MODAL } from '../../../ducks/ui/modal'
import {OptionsState} from "../../../ducks/_options_gen";
describe('optionsEditor reducer', () => {
@ -7,17 +8,17 @@ describe('optionsEditor reducer', () => {
expect(reduceOptionsEditor(undefined, {})).toEqual({})
})
let state = undefined
it('should handle option update start', () => {
state = reduceOptionsEditor(undefined, optionsEditorActions.startUpdate('foo', 'bar'))
let state = reduceOptionsEditor(undefined, optionsEditorActions.startUpdate('foo', 'bar'))
expect(state).toEqual({ foo: {error: false, isUpdating: true, value: 'bar'}})
})
it('should handle option update success', () => {
expect(reduceOptionsEditor(state, optionsEditorActions.updateSuccess('foo'))).toEqual({foo: undefined})
expect(reduceOptionsEditor(undefined, optionsEditorActions.updateSuccess('foo'))).toEqual({foo: undefined})
})
it('should handle option update error', () => {
let state = reduceOptionsEditor(undefined, optionsEditorActions.startUpdate('foo', 'bar'))
state = reduceOptionsEditor(state, optionsEditorActions.updateError('foo', 'errorMsg'))
expect(state).toEqual({ foo: {error: 'errorMsg', isUpdating: false, value: 'bar'}})
// boolean type

View File

@ -1,170 +0,0 @@
import {reduce} from '../../../ducks/utils/store'
import * as storeActions from '../../../ducks/utils/store'
describe('store reducer', () => {
it('should return initial state', () => {
expect(reduce(undefined, {})).toEqual({
byId: {},
list: [],
listIndex: {},
view: [],
viewIndex: {},
})
})
it('should handle add action', () => {
let a = {id: 1},
b = {id: 9},
state = reduce(undefined, {})
expect(state = reduce(state, storeActions.add(a))).toEqual({
byId: { 1: a },
listIndex: { 1: 0 },
list: [ a ],
view: [ a ],
viewIndex: { 1: 0 },
})
expect(state = reduce(state, storeActions.add(b))).toEqual({
byId: { 1: a, 9: b },
listIndex: { 1: 0, 9: 1 },
list: [ a, b ],
view: [ a, b ],
viewIndex: { 1: 0, 9: 1 },
})
// add item and sort them
let c = {id: 0}
expect(reduce(state, storeActions.add(c, undefined,
(a, b) => {return a.id - b.id}))).toEqual({
byId: {...state.byId, 0: c },
list: [...state.list, c ],
listIndex: {...state.listIndex, 0:2},
view: [c, ...state.view ],
viewIndex: {0: 0, 1: 1, 9: 2}
})
})
it('should not add the item with duplicated id', () => {
let a = {id: 1},
state = reduce(undefined, storeActions.add(a))
expect(reduce(state, storeActions.add(a))).toEqual(state)
})
it('should handle update action', () => {
let a = {id: 1, foo: "foo"},
updated = {...a, foo: "bar"},
state = reduce(undefined, storeActions.add(a))
expect(reduce(state, storeActions.update(updated))).toEqual({
byId: { 1: updated },
list: [ updated ],
listIndex: { 1: 0 },
view: [ updated ],
viewIndex: { 1: 0 },
})
})
it('should handle update action with filter', () => {
let a = {id: 0}, b = {id: 1},
state = reduce(undefined, storeActions.receive([a, b]))
state = reduce(state, storeActions.update(b,
item => {return item.id != 1}))
expect(state).toEqual({
byId: { 0: a, 1: b },
list: [ a, b ],
listIndex: { 0: 0, 1: 1 },
view: [ a ],
viewIndex: { 0: 0 }
})
expect(reduce(state, storeActions.update(b,
item => {return item.id != 0}))).toEqual({
byId: { 0: a, 1: b },
list: [ a, b ],
listIndex: { 0: 0, 1: 1 },
view: [ a, b ],
viewIndex: { 0: 0, 1: 1 }
})
})
it('should handle update action with sort', () => {
let a = {id: 2},
b = {id: 3},
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.update(b, undefined,
(a, b) => {return b.id - a.id}))).toEqual({
// sort by id in descending order
byId: { 2: a, 3: b },
list: [ a, b ],
listIndex: {2: 0, 3: 1},
view: [ b, a ],
viewIndex: { 2: 1, 3: 0 },
})
let state1 = reduce(undefined, storeActions.receive([b, a]))
expect(reduce(state1, storeActions.update(b, undefined,
(a, b) => {return a.id - b.id}))).toEqual({
// sort by id in ascending order
byId: { 2: a, 3: b },
list: [ b, a ],
listIndex: {2: 1, 3: 0},
view: [ a, b ],
viewIndex: { 2: 0, 3: 1 },
})
})
it('should set filter', () => {
let a = { id: 1 },
b = { id: 2 },
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.setFilter(
item => {return item.id != 1}
))).toEqual({
byId: { 1 :a, 2: b },
list: [ a, b ],
listIndex:{ 1: 0, 2: 1},
view: [ b ],
viewIndex: { 2: 0 },
})
})
it('should set sort', () => {
let a = { id: 1 },
b = { id: 2 },
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.setSort(
(a, b) => { return b.id - a.id }
))).toEqual({
byId: { 1: a, 2: b },
list: [ a, b ],
listIndex: {1: 0, 2: 1},
view: [ b, a ],
viewIndex: { 1: 1, 2: 0 },
})
})
it('should handle remove action', () => {
let a = { id: 1 }, b = { id: 2},
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.remove(1))).toEqual({
byId: { 2: b },
list: [ b ],
listIndex: { 2: 0 },
view: [ b ],
viewIndex: { 2: 0 },
})
expect(reduce(state, storeActions.remove(3))).toEqual(state)
})
it('should handle receive list', () => {
let a = { id: 1 }, b = { id: 2 },
list = [ a, b ]
expect(reduce(undefined, storeActions.receive(list))).toEqual({
byId: { 1: a, 2: b },
list: [ a, b ],
listIndex: {1: 0, 2: 1},
view: [ a, b ],
viewIndex: { 1: 0, 2: 1 },
})
})
})

View File

@ -0,0 +1,188 @@
import * as storeActions from '../../../ducks/utils/store'
import {Item, reduce} from '../../../ducks/utils/store'
describe('store reducer', () => {
it('should return initial state', () => {
expect(reduce(undefined, {})).toEqual({
byId: {},
list: [],
listIndex: {},
view: [],
viewIndex: {},
})
})
it('should handle add action', () => {
let a = {id: "1"},
b = {id: "9"},
state = reduce(undefined, {})
expect(state = reduce(state, storeActions.add(a))).toEqual({
byId: {"1": a},
listIndex: {"1": 0},
list: [a],
view: [a],
viewIndex: {"1": 0},
})
expect(state = reduce(state, storeActions.add(b))).toEqual({
byId: {"1": a, 9: b},
listIndex: {"1": 0, "9": 1},
list: [a, b],
view: [a, b],
viewIndex: {"1": 0, "9": 1},
})
// add item and sort them
let c = {id: "0"}
expect(reduce(state, storeActions.add(c, undefined,
(a, b) => {
return a.id > b.id ? 1 : -1
}))).toEqual({
byId: {...state.byId, "0": c},
list: [...state.list, c],
listIndex: {...state.listIndex, "0": 2},
view: [c, ...state.view],
viewIndex: {"0": 0, "1": 1, "9": 2}
})
})
it('should not add the item with duplicated id', () => {
let a = {id: "1"},
state = reduce(undefined, storeActions.add(a))
expect(reduce(state, storeActions.add(a))).toEqual(state)
})
it('should handle update action', () => {
interface TItem extends Item {
foo: string
}
let a: TItem = {id: "1", foo: "foo"},
updated = {...a, foo: "bar"},
state = reduce(undefined, storeActions.add(a))
expect(reduce(state, storeActions.update(updated))).toEqual({
byId: {1: updated},
list: [updated],
listIndex: {1: 0},
view: [updated],
viewIndex: {1: 0},
})
})
it('should handle update action with filter', () => {
let a = {id: "0"}, b = {id: "1"},
state = reduce(undefined, storeActions.receive([a, b]))
state = reduce(state, storeActions.update(b,
item => {
return item.id !== "1"
}))
expect(state).toEqual({
byId: {"0": a, "1": b},
list: [a, b],
listIndex: {"0": 0, "1": 1},
view: [a],
viewIndex: {"0": 0}
})
expect(reduce(state, storeActions.update(b,
item => {
return item.id !== "0"
}))).toEqual({
byId: {"0": a, "1": b},
list: [a, b],
listIndex: {"0": 0, "1": 1},
view: [a, b],
viewIndex: {"0": 0, "1": 1}
})
})
it('should handle update action with sort', () => {
let a = {id: "2"},
b = {id: "3"},
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.update(b, undefined,
(a, b) => {
return b.id > a.id ? 1 : -1
}))).toEqual({
// sort by id in descending order
byId: {"2": a, "3": b},
list: [a, b],
listIndex: {"2": 0, "3": 1},
view: [b, a],
viewIndex: {"2": 1, "3": 0},
})
let state1 = reduce(undefined, storeActions.receive([b, a]))
expect(reduce(state1, storeActions.update(b, undefined,
(a, b) => {
return a.id > b.id ? 1 : -1
}))).toEqual({
// sort by id in ascending order
byId: {"2": a, "3": b},
list: [b, a],
listIndex: {"2": 1, "3": 0},
view: [a, b],
viewIndex: {"2": 0, "3": 1},
})
})
it('should set filter', () => {
let a = {id: "1"},
b = {id: "2"},
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.setFilter(
item => {
return item.id !== "1"
}
))).toEqual({
byId: {"1": a, "2": b},
list: [a, b],
listIndex: {"1": 0, "2": 1},
view: [b],
viewIndex: {"2": 0},
})
})
it('should set sort', () => {
let a = {id: "1"},
b = {id: "2"},
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.setSort(
(a, b) => {
return b.id > a.id ? 1 : -1
}
))).toEqual({
byId: {1: a, 2: b},
list: [a, b],
listIndex: {1: 0, 2: 1},
view: [b, a],
viewIndex: {1: 1, 2: 0},
})
})
it('should handle remove action', () => {
let a = {id: "1"}, b = {id: "2"},
state = reduce(undefined, storeActions.receive([a, b]))
expect(reduce(state, storeActions.remove("1"))).toEqual({
byId: {"2": b},
list: [b],
listIndex: {"2": 0},
view: [b],
viewIndex: {"2": 0},
})
expect(reduce(state, storeActions.remove("3"))).toEqual(state)
})
it('should handle receive list', () => {
let a = {id: "1"}, b = {id: "2"},
list = [a, b]
expect(reduce(undefined, storeActions.receive(list))).toEqual({
byId: {"1": a, "2": b},
list: [a, b],
listIndex: {"1": 0, "2": 1},
view: [a, b],
viewIndex: {"1": 0, "2": 1},
})
})
})

View File

@ -1,16 +1,17 @@
import * as utils from '../../flow/utils'
import {TFlow} from "../ducks/tutils";
describe('MessageUtils', () => {
it('should be possible to get first header', () => {
let msg = { headers: [["foo", "bar"]]}
expect(utils.MessageUtils.get_first_header(msg, "foo")).toEqual("bar")
expect(utils.MessageUtils.get_first_header(msg, "123")).toEqual(undefined)
let tflow = TFlow();
expect(utils.MessageUtils.get_first_header(tflow.request, /header/)).toEqual("qvalue")
expect(utils.MessageUtils.get_first_header(tflow.request, /123/)).toEqual(undefined)
})
it('should be possible to get Content-Type', () => {
let type = "text/html",
msg = { headers: [["Content-Type", type]]}
expect(utils.MessageUtils.getContentType(msg)).toEqual(type)
let tflow = TFlow();
tflow.request.headers = [["Content-Type", "text/html"]];
expect(utils.MessageUtils.getContentType(tflow.request)).toEqual("text/html");
})
it('should be possible to match header', () => {
@ -21,28 +22,27 @@ describe('MessageUtils', () => {
})
it('should be possible to get content URL', () => {
const flow = TFlow();
// request
let msg = "foo", view = "bar",
flow = { request: msg, id: 1}
expect(utils.MessageUtils.getContentURL(flow, msg, view)).toEqual(
"./flows/1/request/content/bar.json"
let view = "bar";
expect(utils.MessageUtils.getContentURL(flow, flow.request, view)).toEqual(
"./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/request/content/bar.json"
)
expect(utils.MessageUtils.getContentURL(flow, msg, '')).toEqual(
"./flows/1/request/content.data"
expect(utils.MessageUtils.getContentURL(flow, flow.request, '')).toEqual(
"./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/request/content.data"
)
// response
flow = {response: msg, id: 2}
expect(utils.MessageUtils.getContentURL(flow, msg, view)).toEqual(
"./flows/2/response/content/bar.json"
expect(utils.MessageUtils.getContentURL(flow, flow.response, view)).toEqual(
"./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content/bar.json"
)
})
})
describe('RequestUtils', () => {
it('should be possible prettify url', () => {
let request = {port: 4444, scheme: "http", pretty_host: "foo", path: "/bar"}
expect(utils.RequestUtils.pretty_url(request)).toEqual(
"http://foo:4444/bar"
let flow = TFlow();
expect(utils.RequestUtils.pretty_url(flow.request)).toEqual(
"http://address:22/path"
)
})
})

View File

@ -63,10 +63,10 @@ describe('updateStoreFromUrl', () => {
describe('updateUrlFromStore', () => {
let initialState = {
flows: reduceFlows(undefined, {}),
ui: reduceUI(undefined, {}),
eventLog: reduceEventLog(undefined, {}),
commandBar: reduceCommandBar(undefined, {}),
flows: reduceFlows(undefined, {type: "other"}),
ui: reduceUI(undefined, {type: "other"}),
eventLog: reduceEventLog(undefined, {type: "other"}),
commandBar: reduceCommandBar(undefined, {type: "other"}),
}
it('should update initial url', () => {
@ -76,7 +76,7 @@ describe('updateUrlFromStore', () => {
})
it('should update url', () => {
let flows = reduceFlows(undefined, flowsActions.select(123)),
let flows = reduceFlows(undefined, flowsActions.select("123")),
state = {
...initialState,
flows: reduceFlows(flows, flowsActions.setFilter('~u foo'))
@ -89,10 +89,10 @@ describe('updateUrlFromStore', () => {
describe('initialize', () => {
let initialState = {
flows: reduceFlows(undefined, {}),
ui: reduceUI(undefined, {}),
eventLog: reduceEventLog(undefined, {}),
commandBar: reduceCommandBar(undefined, {}),
flows: reduceFlows(undefined, {type: "other"}),
ui: reduceUI(undefined, {type: "other"}),
eventLog: reduceEventLog(undefined, {type: "other"}),
commandBar: reduceCommandBar(undefined, {type: "other"}),
}
it('should handle initial state', () => {

View File

@ -1,6 +1,7 @@
import * as utils from '../utils'
import {enableFetchMocks} from "jest-fetch-mock";
global.fetch = jest.fn()
enableFetchMocks();
describe('formatSize', () => {
it('should return 0 when 0 byte', () => {
@ -19,9 +20,10 @@ describe('formatTimeDelta', () => {
})
})
describe('formatTimeSTamp', () => {
describe('formatTimeStamp', () => {
it('should return formatted time', () => {
expect(utils.formatTimeStamp(1483228800, false)).toEqual("2017-01-01 00:00:00.000")
expect(utils.formatTimeStamp(1483228800, {milliseconds: false})).toEqual("2017-01-01 00:00:00")
expect(utils.formatTimeStamp(1483228800, {milliseconds: true})).toEqual("2017-01-01 00:00:00.000")
})
})
@ -35,22 +37,22 @@ describe('reverseString', () => {
describe('fetchApi', () => {
it('should handle fetch operation', () => {
utils.fetchApi('http://foo/bar', {method: "POST"})
expect(fetch.mock.calls[0][0]).toEqual(
expect(fetchMock.mock.calls[0][0]).toEqual(
"http://foo/bar"
)
fetch.mockClear()
fetchMock.mockClear()
utils.fetchApi('http://foo?bar=1', {method: "POST"})
expect(fetch.mock.calls[0][0]).toEqual(
expect(fetchMock.mock.calls[0][0]).toEqual(
"http://foo?bar=1"
)
})
it('should be possible to do put request', () => {
fetch.mockClear()
fetchMock.mockClear()
utils.fetchApi.put("http://foo", [1, 2, 3], {})
expect(fetch.mock.calls[0]).toEqual(
expect(fetchMock.mock.calls[0]).toEqual(
[
"http://foo",
{

View File

@ -1,32 +0,0 @@
/*
* This backend uses the REST API only to host static instances,
* without any Websocket connection.
*/
import { fetchApi } from "../utils"
export default class StaticBackend {
constructor(store) {
this.store = store
this.onOpen()
}
onOpen() {
this.fetchData("flows")
this.fetchData("options")
// this.fetchData("events") # TODO: Add events log to static viewer.
}
fetchData(resource) {
fetchApi(`./${resource}`)
.then(res => res.json())
.then(json => {
this.receive(resource, json)
})
}
receive(resource, data) {
let type = `${resource}_RECEIVE`.toUpperCase()
this.store.dispatch({ type, cmd: "receive", resource, data })
}
}

View File

@ -0,0 +1,37 @@
/*
* This backend uses the REST API only to host static instances,
* without any Websocket connection.
*/
import {fetchApi} from "../utils"
import {Store} from "redux";
import {RootState} from "../ducks";
export default class StaticBackend {
store: Store<RootState>
constructor(store) {
this.store = store
this.onOpen()
}
onOpen() {
this.fetchData("flows")
this.fetchData("options")
// this.fetchData("events") # TODO: Add events log to static viewer.
}
fetchData(resource) {
fetchApi(`./${resource}`)
.then(res => res.json())
.then(json => {
this.receive(resource, json)
})
}
receive(resource, data) {
let type = `${resource}_RECEIVE`.toUpperCase()
this.store.dispatch({type, cmd: "receive", resource, data})
}
}

View File

@ -3,12 +3,23 @@
* from the REST API and live updates delivered via a WebSocket connection.
* An alternative backend may use the REST API only to host static instances.
*/
import { fetchApi } from "../utils"
import {fetchApi} from "../utils"
import * as connectionActions from "../ducks/connection"
import {Store} from "redux";
import {RootState} from "../ducks";
const CMD_RESET = 'reset'
export default class WebsocketBackend {
activeFetches: {
flows?: []
events?: []
options?: []
}
store: Store<RootState>
socket: WebSocket
constructor(store) {
this.activeFetches = {}
this.store = store
@ -51,18 +62,18 @@ export default class WebsocketBackend {
this.activeFetches[msg.resource].push(msg)
} else {
let type = `${msg.resource}_${msg.cmd}`.toUpperCase()
this.store.dispatch({ type, ...msg })
this.store.dispatch({type, ...msg})
}
}
receive(resource, data) {
let type = `${resource}_RECEIVE`.toUpperCase()
this.store.dispatch({ type, cmd: "receive", resource, data })
this.store.dispatch({type, cmd: "receive", resource, data})
let queue = this.activeFetches[resource]
delete this.activeFetches[resource]
queue.forEach(msg => this.onMessage(msg))
if(Object.keys(this.activeFetches).length === 0) {
if (Object.keys(this.activeFetches).length === 0) {
// We have fetched the last resource
this.store.dispatch(connectionActions.connectionEstablished())
}
@ -75,7 +86,7 @@ export default class WebsocketBackend {
console.error("websocket connection closed", closeEvent)
}
onError() {
onError(error) {
// FIXME
console.error("websocket connection errored", arguments)
}

View File

@ -1,6 +1,6 @@
import React, {useEffect, useRef, useState} from 'react'
import classnames from 'classnames'
import {fetchApi, Key, runCommand} from '../utils'
import {fetchApi, runCommand} from '../utils'
import Filt from '../filt/command'
type CommandParameter = {
@ -151,7 +151,7 @@ export default function CommandBar() {
}
const onKeyDown = (e) => {
if (e.keyCode === Key.ENTER) {
if (e.key === "Enter") {
const [cmd, ...args] = Filt.parse(input);
setHistory([...history, input]);
@ -184,7 +184,7 @@ export default function CommandBar() {
setCurrentCompletion(0)
setCompletionCandidate(availableCommands)
}
if (e.keyCode === Key.UP) {
if (e.key === "ArrowUp") {
let nextPos;
if (currentPos === undefined) {
nextPos = history.length - 1;
@ -195,7 +195,7 @@ export default function CommandBar() {
setOriginalInput(history[nextPos])
setCurrentPos(nextPos)
}
if (e.keyCode === Key.DOWN) {
if (e.key === "ArrowDown") {
if (currentPos === undefined) {
return
} else if (currentPos == history.length - 1) {
@ -209,7 +209,7 @@ export default function CommandBar() {
setCurrentPos(nextPos)
}
}
if (e.keyCode === Key.TAB) {
if (e.key === "Tab") {
setInput(completionCandidate[currentCompletion])
setCurrentCompletion((currentCompletion + 1) % completionCandidate.length)
e.preventDefault()

View File

@ -3,9 +3,19 @@ import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import shallowEqual from 'shallowequal'
import AutoScroll from '../helpers/AutoScroll'
import { calcVScroll } from '../helpers/VirtualScroll'
import {calcVScroll, VScroll} from '../helpers/VirtualScroll'
import {EventLogItem} from "../../ducks/eventLog";
class EventLogList extends Component {
type EventLogListProps = {
events: EventLogItem[]
rowHeight: number
}
type EventLogListState = {
vScroll: VScroll
}
class EventLogList extends Component<EventLogListProps, EventLogListState> {
static propTypes = {
events: PropTypes.array.isRequired,
@ -16,6 +26,8 @@ class EventLogList extends Component {
rowHeight: 18,
}
heights: {[id: string]: number}
constructor(props) {
super(props)
@ -70,14 +82,14 @@ class EventLogList extends Component {
return (
<pre onScroll={this.onViewportUpdate}>
<div style={{ height: vScroll.paddingTop }}></div>
<div style={{ height: vScroll.paddingTop }}/>
{events.slice(vScroll.start, vScroll.end).map(event => (
<div key={event.id} ref={node => this.setHeight(event.id, node)}>
<LogIcon event={event}/>
{event.message}
</div>
))}
<div style={{ height: vScroll.paddingBottom }}></div>
<div style={{ height: vScroll.paddingBottom }}/>
</pre>
)
}
@ -90,7 +102,7 @@ function LogIcon({ event }) {
warn: 'exclamation-triangle',
error: 'ban'
}[event.level] || 'info'
return <i className={`fa fa-fw fa-${icon}`}></i>
return <i className={`fa fa-fw fa-${icon}`}/>
}
export default AutoScroll(EventLogList)

View File

@ -1,7 +1,6 @@
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
import classnames from 'classnames'
import {Key} from '../../utils'
import Filt from '../../filt/filt'
import FilterDocs from './FilterDocs'
@ -91,7 +90,7 @@ export default class FilterInput extends Component<FilterInputProps, FilterInput
}
onKeyDown(e) {
if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) {
if (e.key === "Escape" || e.key === "Enter") {
this.blur()
// If closed using ESC/ENTER, hide the tooltip.
this.setState({mousefocus: false})

View File

@ -1,12 +1,11 @@
import React, { Component } from "react"
import React, {Component} from "react"
import PropTypes from "prop-types"
import { connect } from "react-redux"
import { update as updateOptions } from "../../ducks/options"
import { Key } from "../../utils"
import {connect} from "react-redux"
import {update as updateOptions} from "../../ducks/options"
import classnames from 'classnames'
const stopPropagation = e => {
if (e.keyCode !== Key.ESC) {
if (e.key !== "Escape") {
e.stopPropagation()
}
}
@ -15,7 +14,8 @@ BooleanOption.propTypes = {
value: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
}
function BooleanOption({ value, onChange, ...props }) {
function BooleanOption({value, onChange, ...props}) {
return (
<div className="checkbox">
<label>
@ -34,7 +34,8 @@ StringOption.propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
}
function StringOption({ value, onChange, ...props }) {
function StringOption({value, onChange, ...props}) {
return (
<input type="text"
value={value || ""}
@ -43,8 +44,9 @@ function StringOption({ value, onChange, ...props }) {
/>
)
}
function Optional(Component) {
return function ({ onChange, ...props }) {
return function ({onChange, ...props}) {
return <Component
onChange={x => onChange(x ? x : null)}
{...props}
@ -56,7 +58,8 @@ NumberOption.propTypes = {
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
}
function NumberOption({ value, onChange, ...props }) {
function NumberOption({value, onChange, ...props}) {
return (
<input type="number"
value={value}
@ -70,14 +73,15 @@ ChoicesOption.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
}
export function ChoicesOption({ value, onChange, choices, ...props }) {
export function ChoicesOption({value, onChange, choices, ...props}) {
return (
<select
onChange={(e) => onChange(e.target.value)}
value={value}
{...props}
>
{ choices.map(
{choices.map(
choice => (
<option key={choice} value={choice}>{choice}</option>
)
@ -90,7 +94,8 @@ StringSequenceOption.propTypes = {
value: PropTypes.arrayOf(PropTypes.string).isRequired,
onChange: PropTypes.func.isRequired,
}
function StringSequenceOption({ value, onChange, ...props }) {
function StringSequenceOption({value, onChange, ...props}) {
const height = Math.max(value.length, 1)
return <textarea
rows={height}
@ -108,21 +113,21 @@ export const Options = {
"sequence of str": StringSequenceOption,
}
function PureOption({ choices, type, value, onChange, name, error }) {
function PureOption({choices, type, value, onChange, name, error}) {
let Opt, props = {}
if (choices) {
Opt = ChoicesOption;
props.choices = choices
} else {
Opt = Options[type]
if(!Opt)
if (!Opt)
throw `unknown option type ${type}`
}
if (Opt !== BooleanOption) {
props.className = "form-control"
}
return <div className={classnames({'has-error':error})}>
return <div className={classnames({'has-error': error})}>
<Opt
name={name}
value={value}
@ -132,12 +137,13 @@ function PureOption({ choices, type, value, onChange, name, error }) {
/>
</div>
}
export default connect(
(state, { name }) => ({
(state, {name}) => ({
...state.options_meta[name],
...state.ui.optionsEditor[name]
}),
(dispatch, { name }) => ({
(dispatch, {name}) => ({
onChange: value => dispatch(updateOptions(name, value))
})
)(PureOption)

View File

@ -1,55 +0,0 @@
import React, { Component } from 'react'
import classnames from 'classnames'
import { Key } from '../../utils'
type ToggleInputButtonProps = {
name: string,
txt: string,
onToggleChanged: Function,
checked: boolean,
placeholder: string,
inputType: string,
}
type ToggleInputButtonState = {
txt: string,
}
export default class ToggleInputButton extends Component<ToggleInputButtonProps, ToggleInputButtonState> {
constructor(props) {
super(props)
this.state = { txt: props.txt || '' }
}
onKeyDown(e) {
e.stopPropagation()
if (e.keyCode === Key.ENTER) {
this.props.onToggleChanged(this.state.txt)
}
}
render() {
const {checked, onToggleChanged, name, inputType, placeholder} = this.props
return (
<div className="input-group toggle-input-btn">
<span className="input-group-btn"
onClick={() => onToggleChanged(this.state.txt)}>
<div className={classnames('btn', checked ? 'btn-primary' : 'btn-default')}>
<span className={classnames('fa', checked ? 'fa-check-square-o' : 'fa-square-o')}/>
&nbsp;
{name}
</div>
</span>
<input
className="form-control"
placeholder={placeholder}
disabled={checked}
value={this.state.txt}
type={inputType || 'text'}
onChange={e => this.setState({ txt: e.target.value })}
onKeyDown={e => this.onKeyDown(e)}
/>
</div>
)
}
}

View File

@ -1,8 +1,6 @@
import React, {Component} from 'react'
import classnames from 'classnames'
import {Key} from '../../utils'
export interface ValueEditorProps {
content: string
onEditDone: (newVal: string) => void
@ -157,13 +155,13 @@ export default class ValueEditor extends Component<ValueEditorProps> {
onKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
EVENT_DEBUG && console.debug("keydown", e)
e.stopPropagation()
switch (e.keyCode) {
case Key.ESC:
switch (e.key) {
case "Escape":
e.preventDefault()
this.resetValue()
this.finishEditing()
break
case Key.ENTER:
case "Enter":
if (!e.shiftKey) {
e.preventDefault()
this.finishEditing()

View File

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
const symShouldStick = Symbol("shouldStick");
const symShouldStick = Symbol("shouldStick") as any;
const isAtBottom = v => v.scrollTop + v.clientHeight === v.scrollHeight;
export default Component => Object.assign(class AutoScrollWrapper extends Component {

View File

@ -18,7 +18,23 @@
* - {number} paddingTop
* - {number} paddingBottom
*/
export function calcVScroll(opts) {
type VScrollArgs = {
itemCount: number
rowHeight: number
viewportTop: number
viewportHeight: number
itemHeights?: number[]
}
export type VScroll = {
start: number
end: number
paddingTop: number
paddingBottom: number
}
export function calcVScroll(opts: VScrollArgs | undefined = undefined) {
if (!opts) {
return { start: 0, end: 0, paddingTop: 0, paddingBottom: 0 };
}

View File

@ -1,5 +1,3 @@
import {Reducer} from "redux";
export const TOGGLE_VISIBILITY = 'COMMANDBAR_TOGGLE_VISIBILITY'
interface CommandBarState {
@ -10,7 +8,7 @@ export const defaultState: CommandBarState = {
visible: false,
};
const reducer: Reducer<CommandBarState> = (state = defaultState, action): CommandBarState => {
export default function reducer(state = defaultState, action): CommandBarState {
switch (action.type) {
case TOGGLE_VISIBILITY:
return {
@ -22,8 +20,7 @@ const reducer: Reducer<CommandBarState> = (state = defaultState, action): Comman
return state
}
}
export default reducer
export function toggleVisibility() {
return { type: TOGGLE_VISIBILITY }
return {type: TOGGLE_VISIBILITY}
}

View File

@ -2,7 +2,6 @@
* Conf houses properties about the current mitmproxy instance that are not options,
* e.g. the list of available content views or the current version.
*/
import {Reducer} from "redux";
interface ConfState {
static: boolean
@ -17,7 +16,6 @@ export const defaultState: ConfState = window.MITMWEB_CONF || {
contentViews: ["Auto", "Raw"],
};
const reducer: Reducer<ConfState> = (state = defaultState, action): ConfState => {
export default function reducer(state = defaultState, action): ConfState {
return state
}
export default reducer

View File

@ -1,5 +1,3 @@
import {Reducer} from "redux";
export enum ConnectionState {
INIT = "CONNECTION_INIT",
FETCHING = "CONNECTION_FETCHING", // WebSocket is established, but still fetching resources.
@ -19,7 +17,7 @@ const defaultState: ConnState = {
message: undefined,
}
const reducer: Reducer<ConnState> = (state = defaultState, action) => {
export default function reducer(state = defaultState, action): ConnState {
switch (action.type) {
case ConnectionState.ESTABLISHED:
case ConnectionState.FETCHING:
@ -34,7 +32,6 @@ const reducer: Reducer<ConnState> = (state = defaultState, action) => {
return state
}
}
export default reducer
export function startFetching() {
return {type: ConnectionState.FETCHING}

View File

@ -7,7 +7,7 @@ export const TOGGLE_FILTER = 'EVENTS_TOGGLE_FILTER'
type LogLevel = 'debug' | 'info' | 'web' | 'warn' | 'error';
interface EventLogItem extends store.Item {
export interface EventLogItem extends store.Item {
message: string
level: LogLevel
}

View File

@ -28,7 +28,7 @@ export interface FlowsState extends store.State<Flow> {
selected: string[],
}
const defaultState: FlowsState = {
export const defaultState: FlowsState = {
highlight: undefined,
filter: undefined,
sort: {column: undefined, desc: false},
@ -36,7 +36,7 @@ const defaultState: FlowsState = {
...store.defaultState
}
export default function reduce(state: FlowsState = defaultState, action): FlowsState {
export default function reducer(state: FlowsState = defaultState, action): FlowsState {
switch (action.type) {
case ADD:

View File

@ -1,7 +1,5 @@
import {fetchApi} from "../utils"
import * as optionsEditorActions from "./ui/optionsEditor"
import _ from "lodash"
import {Reducer} from "redux";
import {defaultState, Option, OptionsState} from "./_options_gen";
import {AppThunk} from "./index";
@ -11,8 +9,7 @@ export const REQUEST_UPDATE = 'REQUEST_UPDATE'
export {Option, defaultState}
const reducer: Reducer<OptionsState> = (state = defaultState, action) => {
export default function reducer(state = defaultState, action): OptionsState {
switch (action.type) {
case RECEIVE:
let s = <OptionsState>{};
@ -34,7 +31,6 @@ const reducer: Reducer<OptionsState> = (state = defaultState, action) => {
return state
}
}
export default reducer
export async function pureSendUpdate(option: Option, value, dispatch) {
try {

View File

@ -1,5 +1,3 @@
import {Reducer} from "redux";
export const
SET_TAB = "UI_FLOWVIEW_SET_TAB",
SET_CONTENT_VIEW_FOR = "SET_CONTENT_VIEW_FOR"
@ -15,8 +13,7 @@ export const defaultState: UiFlowState = {
contentViewFor: {},
}
const reducer: Reducer<UiFlowState> = (state = defaultState, action): UiFlowState => {
export default function reducer(state = defaultState, action): UiFlowState {
switch (action.type) {
case SET_CONTENT_VIEW_FOR:
@ -38,7 +35,6 @@ const reducer: Reducer<UiFlowState> = (state = defaultState, action): UiFlowStat
return state
}
}
export default reducer;
export function selectTab(tab) {
return {type: SET_TAB, tab}

View File

@ -1,18 +1,16 @@
import { Key } from "../../utils"
import { selectTab } from "./flow"
import {selectTab} from "./flow"
import * as flowsActions from "../flows"
import * as modalActions from "./modal"
import {tabsForFlow} from "../../components/FlowView";
export function onKeyDown(e) {
export function onKeyDown(e: KeyboardEvent) {
//console.debug("onKeyDown", e)
if (e.ctrlKey || e.metaKey) {
return () => {
}
}
let key = e.keyCode,
shiftKey = e.shiftKey
const key = e.key;
e.preventDefault()
return (dispatch, getState) => {
@ -20,42 +18,42 @@ export function onKeyDown(e) {
flow = flows.byId[getState().flows.selected[0]]
switch (key) {
case Key.K:
case Key.UP:
case "k":
case "ArrowUp":
dispatch(flowsActions.selectRelative(flows, -1))
break
case Key.J:
case Key.DOWN:
case "j":
case "ArrowDown":
dispatch(flowsActions.selectRelative(flows, +1))
break
case Key.SPACE:
case Key.PAGE_DOWN:
case " ":
case "PageDown":
dispatch(flowsActions.selectRelative(flows, +10))
break
case Key.PAGE_UP:
case "PageUp":
dispatch(flowsActions.selectRelative(flows, -10))
break
case Key.END:
case "End":
dispatch(flowsActions.selectRelative(flows, +1e10))
break
case Key.HOME:
case "Home":
dispatch(flowsActions.selectRelative(flows, -1e10))
break
case Key.ESC:
if(getState().ui.modal.activeModal){
case "Escape":
if (getState().ui.modal.activeModal) {
dispatch(modalActions.hideModal())
} else {
dispatch(flowsActions.select(null))
dispatch(flowsActions.select(undefined))
}
break
case Key.LEFT: {
case "ArrowLeft": {
if (!flow) break
let tabs = tabsForFlow(flow),
currentTab = getState().ui.flow.tab,
@ -64,8 +62,8 @@ export function onKeyDown(e) {
break
}
case Key.TAB:
case Key.RIGHT: {
case "Tab":
case "ArrowRight": {
if (!flow) break
let tabs = tabsForFlow(flow),
currentTab = getState().ui.flow.tab,
@ -74,57 +72,62 @@ export function onKeyDown(e) {
break
}
case Key.D: {
case "d": {
if (!flow) {
return
}
if (shiftKey) {
dispatch(flowsActions.duplicate(flow))
} else {
dispatch(flowsActions.remove(flow))
}
break
}
case Key.A: {
if (shiftKey) {
dispatch(flowsActions.resumeAll())
} else if (flow && flow.intercepted) {
case "D": {
if (!flow) {
return
}
dispatch(flowsActions.duplicate(flow))
break
}
case "a": {
if (flow && flow.intercepted) {
dispatch(flowsActions.resume(flow))
}
break
}
case "A": {
dispatch(flowsActions.resumeAll())
break
}
case Key.R: {
if (!shiftKey && flow) {
case "r": {
if (flow) {
dispatch(flowsActions.replay(flow))
}
break
}
case Key.V: {
if (!shiftKey && flow && flow.modified) {
case "v": {
if (flow && flow.modified) {
dispatch(flowsActions.revert(flow))
}
break
}
case Key.X: {
if (shiftKey) {
dispatch(flowsActions.killAll())
} else if (flow && flow.intercepted) {
case "x": {
if (flow && flow.intercepted) {
dispatch(flowsActions.kill(flow))
}
break
}
case Key.Z: {
if (!shiftKey) {
dispatch(flowsActions.clear())
}
case "X": {
dispatch(flowsActions.killAll())
break
}
case "z": {
dispatch(flowsActions.clear())
break
}
default:
return

View File

@ -52,7 +52,7 @@ export const defaultState = {
* }
*
*/
export function reduce<S extends Item>(state: State<S> = defaultState, action) {
export function reduce<S extends Item>(state: State<S> = defaultState, action): State<S> {
let {byId, list, listIndex, view, viewIndex} = state

View File

@ -5,28 +5,6 @@ import * as React from 'react'
window._ = _;
window.React = React;
export var Key = {
UP: 38,
DOWN: 40,
PAGE_UP: 33,
PAGE_DOWN: 34,
HOME: 36,
END: 35,
LEFT: 37,
RIGHT: 39,
ENTER: 13,
ESC: 27,
TAB: 9,
SPACE: 32,
BACKSPACE: 8,
SHIFT: 16
};
// Add A-Z
for (var i = 65; i <= 90; i++) {
Key[String.fromCharCode(i)] = i;
}
export var formatSize = function (bytes) {
if (bytes === 0)
return "0";
@ -64,7 +42,7 @@ export var formatTimeStamp = function (
) {
let utc = new Date(seconds * 1000);
let ts = utc.toISOString().replace("T", " ").replace("Z", "");
if(!milliseconds)
if (!milliseconds)
ts = ts.slice(0, -4);
return ts;
};