mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-25 01:29:48 +00:00
web: convert everything to TypeScript
This commit is contained in:
parent
c5e3e3d636
commit
3589ec2f58
@ -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()
|
||||
})
|
||||
})
|
20
web/src/js/__tests__/components/Header/FilterDocsSpec.tsx
Normal file
20
web/src/js/__tests__/components/Header/FilterDocsSpec.tsx
Normal 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();
|
||||
|
||||
})
|
@ -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)
|
@ -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"
|
||||
/>
|
||||
`;
|
@ -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>
|
||||
`;
|
@ -24,6 +24,7 @@ exports[`FilterInput Component should render correctly 1`] = `
|
||||
onKeyDown={[Function]}
|
||||
placeholder="bar"
|
||||
type="text"
|
||||
value="42"
|
||||
/>
|
||||
</div>
|
||||
`;
|
@ -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>
|
||||
),
|
@ -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', () => {
|
@ -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()
|
@ -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")
|
||||
})
|
||||
})
|
@ -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
|
@ -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>
|
||||
`;
|
@ -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
|
||||
})
|
||||
})
|
@ -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,
|
||||
})
|
@ -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()
|
@ -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()
|
||||
})
|
||||
})
|
@ -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} ])
|
||||
})
|
||||
|
||||
})
|
166
web/src/js/__tests__/ducks/ui/keyboardSpec.tsx
Normal file
166
web/src/js/__tests__/ducks/ui/keyboardSpec.tsx
Normal 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}])
|
||||
})
|
||||
|
||||
})
|
@ -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 }
|
||||
)
|
@ -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
|
@ -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 },
|
||||
})
|
||||
})
|
||||
})
|
188
web/src/js/__tests__/ducks/utils/storeSpec.tsx
Normal file
188
web/src/js/__tests__/ducks/utils/storeSpec.tsx
Normal 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},
|
||||
})
|
||||
})
|
||||
})
|
@ -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"
|
||||
)
|
||||
})
|
||||
})
|
@ -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', () => {
|
@ -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",
|
||||
{
|
@ -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 })
|
||||
}
|
||||
|
||||
}
|
37
web/src/js/backends/static.tsx
Normal file
37
web/src/js/backends/static.tsx
Normal 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})
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
|
@ -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)
|
@ -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})
|
||||
|
@ -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,36 +113,37 @@ 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})}>
|
||||
<Opt
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={stopPropagation}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
return <div className={classnames({'has-error': error})}>
|
||||
<Opt
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={stopPropagation}
|
||||
{...props}
|
||||
/>
|
||||
</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)
|
||||
|
@ -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')}/>
|
||||
|
||||
{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>
|
||||
)
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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 {
|
@ -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 };
|
||||
}
|
@ -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}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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}
|
||||
|
@ -1,130 +1,133 @@
|
||||
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) => {
|
||||
|
||||
const flows = getState().flows,
|
||||
flow = flows.byId[getState().flows.selected[0]]
|
||||
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),
|
||||
let tabs = tabsForFlow(flow),
|
||||
currentTab = getState().ui.flow.tab,
|
||||
nextTab = tabs[(tabs.indexOf(currentTab) - 1 + tabs.length) % tabs.length]
|
||||
nextTab = tabs[(tabs.indexOf(currentTab) - 1 + tabs.length) % tabs.length]
|
||||
dispatch(selectTab(nextTab))
|
||||
break
|
||||
}
|
||||
|
||||
case Key.TAB:
|
||||
case Key.RIGHT: {
|
||||
case "Tab":
|
||||
case "ArrowRight": {
|
||||
if (!flow) break
|
||||
let tabs = tabsForFlow(flow),
|
||||
let tabs = tabsForFlow(flow),
|
||||
currentTab = getState().ui.flow.tab,
|
||||
nextTab = tabs[(tabs.indexOf(currentTab) + 1) % tabs.length]
|
||||
nextTab = tabs[(tabs.indexOf(currentTab) + 1) % tabs.length]
|
||||
dispatch(selectTab(nextTab))
|
||||
break
|
||||
}
|
||||
|
||||
case Key.D: {
|
||||
case "d": {
|
||||
if (!flow) {
|
||||
return
|
||||
}
|
||||
if (shiftKey) {
|
||||
dispatch(flowsActions.duplicate(flow))
|
||||
} else {
|
||||
dispatch(flowsActions.remove(flow))
|
||||
}
|
||||
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
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user