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 FilterDocs from '../../../components/Header/FilterDocs'
|
||||||
import TestUtil from 'react-dom/test-utils'
|
import TestUtil from 'react-dom/test-utils'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { Key } from '../../../utils'
|
|
||||||
|
|
||||||
describe('FilterInput Component', () => {
|
describe('FilterInput Component', () => {
|
||||||
it('should render correctly', () => {
|
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()
|
tree = filterInput.toJSON()
|
||||||
expect(tree).toMatchSnapshot()
|
expect(tree).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
@ -69,7 +70,7 @@ describe('FilterInput Component', () => {
|
|||||||
it('should handle keyDown', () => {
|
it('should handle keyDown', () => {
|
||||||
input.blur = jest.fn()
|
input.blur = jest.fn()
|
||||||
let mockEvent = {
|
let mockEvent = {
|
||||||
keyCode: Key.ESC,
|
key: "Escape",
|
||||||
stopPropagation: jest.fn()
|
stopPropagation: jest.fn()
|
||||||
}
|
}
|
||||||
filterInput.onKeyDown(mockEvent)
|
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]}
|
onKeyDown={[Function]}
|
||||||
placeholder="bar"
|
placeholder="bar"
|
||||||
type="text"
|
type="text"
|
||||||
|
value="42"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
@ -16,7 +16,7 @@ describe('Button Component', () => {
|
|||||||
|
|
||||||
it('should be able to be disabled', () => {
|
it('should be able to be disabled', () => {
|
||||||
let button = renderer.create(
|
let button = renderer.create(
|
||||||
<Button className="classname" onClick={() => "onclick"} disabled="true" children="children">
|
<Button className="classname" onClick={() => "onclick"} disabled>
|
||||||
<a>foo</a>
|
<a>foo</a>
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
@ -7,12 +7,12 @@ import TestUtils from 'react-dom/test-utils';
|
|||||||
describe('Splitter Component', () => {
|
describe('Splitter Component', () => {
|
||||||
|
|
||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
let splitter = renderer.create(<Splitter></Splitter>),
|
let splitter = renderer.create(<Splitter/>),
|
||||||
tree = splitter.toJSON()
|
tree = splitter.toJSON()
|
||||||
expect(tree).toMatchSnapshot()
|
expect(tree).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
let splitter = TestUtils.renderIntoDocument(<Splitter></Splitter>),
|
let splitter = TestUtils.renderIntoDocument(<Splitter/>),
|
||||||
dom = ReactDOM.findDOMNode(splitter),
|
dom = ReactDOM.findDOMNode(splitter),
|
||||||
previousElementSibling = {
|
previousElementSibling = {
|
||||||
offsetHeight: 0,
|
offsetHeight: 0,
|
||||||
@ -56,15 +56,15 @@ describe('Splitter Component', () => {
|
|||||||
splitter.onMouseMove({pageX: 10, pageY: 10})
|
splitter.onMouseMove({pageX: 10, pageY: 10})
|
||||||
expect(dom.style.transform).toEqual("translate(9px, 0px)")
|
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})
|
splitterY.onMouseMove({pageX: 10, pageY: 10})
|
||||||
expect(ReactDOM.findDOMNode(splitterY).style.transform).toEqual("translate(0px, 10px)")
|
expect(ReactDOM.findDOMNode(splitterY).style.transform).toEqual("translate(0px, 10px)")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle resize', () => {
|
it('should handle resize', () => {
|
||||||
window.setTimeout = jest.fn((event, time) => event())
|
let x = jest.spyOn(window, 'setTimeout');
|
||||||
splitter.onResize()
|
splitter.onResize()
|
||||||
expect(window.setTimeout).toHaveBeenCalled()
|
expect(x).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle componentWillUnmount', () => {
|
it('should handle componentWillUnmount', () => {
|
@ -7,18 +7,14 @@ describe('ToggleButton Component', () => {
|
|||||||
|
|
||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
let checkedButton = renderer.create(
|
let checkedButton = renderer.create(
|
||||||
<ToggleButton checked={true} onToggle={mockFunc} text="foo">
|
<ToggleButton checked={true} onToggle={mockFunc} text="foo"/>),
|
||||||
text
|
|
||||||
</ToggleButton>),
|
|
||||||
tree = checkedButton.toJSON()
|
tree = checkedButton.toJSON()
|
||||||
expect(tree).toMatchSnapshot()
|
expect(tree).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle click action', () => {
|
it('should handle click action', () => {
|
||||||
let uncheckButton = renderer.create(
|
let uncheckButton = renderer.create(
|
||||||
<ToggleButton checked={false} onToggle={mockFunc} text="foo">
|
<ToggleButton checked={false} onToggle={mockFunc} text="foo"/>),
|
||||||
text
|
|
||||||
</ToggleButton>),
|
|
||||||
tree = uncheckButton.toJSON()
|
tree = uncheckButton.toJSON()
|
||||||
tree.props.onClick()
|
tree.props.onClick()
|
||||||
expect(mockFunc).toBeCalled()
|
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`] = `
|
exports[`Button Component should be able to be disabled 1`] = `
|
||||||
<button
|
<button
|
||||||
className="classname btn btn-default"
|
className="classname btn btn-default"
|
||||||
disabled="true"
|
disabled={true}
|
||||||
>
|
>
|
||||||
<a>
|
<a>
|
||||||
foo
|
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', () => {
|
it('should calculate position with itemHeights', () => {
|
||||||
expect(calcVScroll({itemCount: 5, itemHeights: [100, 100, 100, 100, 100],
|
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
|
start: 0, end: 4, paddingTop: 0, paddingBottom: 100
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -4,7 +4,7 @@ import { ConnectionState } from "../../ducks/connection"
|
|||||||
|
|
||||||
describe('connection reducer', () => {
|
describe('connection reducer', () => {
|
||||||
it('should return initial state', () => {
|
it('should return initial state', () => {
|
||||||
expect(reduceConnection(undefined, {})).toEqual({
|
expect(reduceConnection(undefined, {type: "other"})).toEqual({
|
||||||
state: ConnectionState.INIT,
|
state: ConnectionState.INIT,
|
||||||
message: undefined,
|
message: undefined,
|
||||||
})
|
})
|
@ -2,7 +2,7 @@ import {rootReducer} from '../../ducks/index'
|
|||||||
|
|
||||||
describe('reduceState in js/ducks/index.js', () => {
|
describe('reduceState in js/ducks/index.js', () => {
|
||||||
it('should combine flow and header', () => {
|
it('should combine flow and header', () => {
|
||||||
let state = rootReducer(undefined, {})
|
let state = rootReducer(undefined, {type: "other"})
|
||||||
expect(state.hasOwnProperty('eventLog')).toBeTruthy()
|
expect(state.hasOwnProperty('eventLog')).toBeTruthy()
|
||||||
expect(state.hasOwnProperty('flows')).toBeTruthy()
|
expect(state.hasOwnProperty('flows')).toBeTruthy()
|
||||||
expect(state.hasOwnProperty('connection')).toBeTruthy()
|
expect(state.hasOwnProperty('connection')).toBeTruthy()
|
@ -2,7 +2,7 @@ import reduceUI from '../../../ducks/ui/index'
|
|||||||
|
|
||||||
describe('reduceUI in js/ducks/ui/index.js', () => {
|
describe('reduceUI in js/ducks/ui/index.js', () => {
|
||||||
it('should combine flow and header', () => {
|
it('should combine flow and header', () => {
|
||||||
let state = reduceUI(undefined, {})
|
let state = reduceUI(undefined, {type: "other"})
|
||||||
expect(state.hasOwnProperty('flow')).toBeTruthy()
|
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'
|
import reduceModal, * as ModalActions from '../../../ducks/ui/modal'
|
||||||
|
|
||||||
describe('modal reducer', () => {
|
describe('modal reducer', () => {
|
||||||
let state = undefined
|
|
||||||
|
|
||||||
it('should return the initial state', () => {
|
it('should return the initial state', () => {
|
||||||
expect(reduceModal(undefined, {})).toEqual(
|
expect(reduceModal(undefined, {})).toEqual(
|
||||||
@ -10,14 +9,14 @@ describe('modal reducer', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle setActiveModal action', () => {
|
it('should handle setActiveModal action', () => {
|
||||||
state = reduceModal(undefined, ModalActions.setActiveModal('foo'))
|
let state = reduceModal(undefined, ModalActions.setActiveModal('foo'))
|
||||||
expect(state).toEqual(
|
expect(state).toEqual(
|
||||||
{ activeModal: 'foo' }
|
{ activeModal: 'foo' }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle hideModal action', () => {
|
it('should handle hideModal action', () => {
|
||||||
state = reduceModal(state, ModalActions.hideModal())
|
let state = reduceModal(undefined, ModalActions.hideModal())
|
||||||
expect(state).toEqual(
|
expect(state).toEqual(
|
||||||
{ activeModal: undefined }
|
{ activeModal: undefined }
|
||||||
)
|
)
|
@ -1,5 +1,6 @@
|
|||||||
import reduceOptionsEditor, * as optionsEditorActions from '../../../ducks/ui/optionsEditor'
|
import reduceOptionsEditor, * as optionsEditorActions from '../../../ducks/ui/optionsEditor'
|
||||||
import { HIDE_MODAL } from '../../../ducks/ui/modal'
|
import { HIDE_MODAL } from '../../../ducks/ui/modal'
|
||||||
|
import {OptionsState} from "../../../ducks/_options_gen";
|
||||||
|
|
||||||
describe('optionsEditor reducer', () => {
|
describe('optionsEditor reducer', () => {
|
||||||
|
|
||||||
@ -7,17 +8,17 @@ describe('optionsEditor reducer', () => {
|
|||||||
expect(reduceOptionsEditor(undefined, {})).toEqual({})
|
expect(reduceOptionsEditor(undefined, {})).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
let state = undefined
|
|
||||||
it('should handle option update start', () => {
|
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'}})
|
expect(state).toEqual({ foo: {error: false, isUpdating: true, value: 'bar'}})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle option update success', () => {
|
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', () => {
|
it('should handle option update error', () => {
|
||||||
|
let state = reduceOptionsEditor(undefined, optionsEditorActions.startUpdate('foo', 'bar'))
|
||||||
state = reduceOptionsEditor(state, optionsEditorActions.updateError('foo', 'errorMsg'))
|
state = reduceOptionsEditor(state, optionsEditorActions.updateError('foo', 'errorMsg'))
|
||||||
expect(state).toEqual({ foo: {error: 'errorMsg', isUpdating: false, value: 'bar'}})
|
expect(state).toEqual({ foo: {error: 'errorMsg', isUpdating: false, value: 'bar'}})
|
||||||
// boolean type
|
// 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 * as utils from '../../flow/utils'
|
||||||
|
import {TFlow} from "../ducks/tutils";
|
||||||
|
|
||||||
describe('MessageUtils', () => {
|
describe('MessageUtils', () => {
|
||||||
it('should be possible to get first header', () => {
|
it('should be possible to get first header', () => {
|
||||||
let msg = { headers: [["foo", "bar"]]}
|
let tflow = TFlow();
|
||||||
expect(utils.MessageUtils.get_first_header(msg, "foo")).toEqual("bar")
|
expect(utils.MessageUtils.get_first_header(tflow.request, /header/)).toEqual("qvalue")
|
||||||
expect(utils.MessageUtils.get_first_header(msg, "123")).toEqual(undefined)
|
expect(utils.MessageUtils.get_first_header(tflow.request, /123/)).toEqual(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to get Content-Type', () => {
|
it('should be possible to get Content-Type', () => {
|
||||||
let type = "text/html",
|
let tflow = TFlow();
|
||||||
msg = { headers: [["Content-Type", type]]}
|
tflow.request.headers = [["Content-Type", "text/html"]];
|
||||||
expect(utils.MessageUtils.getContentType(msg)).toEqual(type)
|
expect(utils.MessageUtils.getContentType(tflow.request)).toEqual("text/html");
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to match header', () => {
|
it('should be possible to match header', () => {
|
||||||
@ -21,28 +22,27 @@ describe('MessageUtils', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to get content URL', () => {
|
it('should be possible to get content URL', () => {
|
||||||
|
const flow = TFlow();
|
||||||
// request
|
// request
|
||||||
let msg = "foo", view = "bar",
|
let view = "bar";
|
||||||
flow = { request: msg, id: 1}
|
expect(utils.MessageUtils.getContentURL(flow, flow.request, view)).toEqual(
|
||||||
expect(utils.MessageUtils.getContentURL(flow, msg, view)).toEqual(
|
"./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/request/content/bar.json"
|
||||||
"./flows/1/request/content/bar.json"
|
|
||||||
)
|
)
|
||||||
expect(utils.MessageUtils.getContentURL(flow, msg, '')).toEqual(
|
expect(utils.MessageUtils.getContentURL(flow, flow.request, '')).toEqual(
|
||||||
"./flows/1/request/content.data"
|
"./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/request/content.data"
|
||||||
)
|
)
|
||||||
// response
|
// response
|
||||||
flow = {response: msg, id: 2}
|
expect(utils.MessageUtils.getContentURL(flow, flow.response, view)).toEqual(
|
||||||
expect(utils.MessageUtils.getContentURL(flow, msg, view)).toEqual(
|
"./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content/bar.json"
|
||||||
"./flows/2/response/content/bar.json"
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('RequestUtils', () => {
|
describe('RequestUtils', () => {
|
||||||
it('should be possible prettify url', () => {
|
it('should be possible prettify url', () => {
|
||||||
let request = {port: 4444, scheme: "http", pretty_host: "foo", path: "/bar"}
|
let flow = TFlow();
|
||||||
expect(utils.RequestUtils.pretty_url(request)).toEqual(
|
expect(utils.RequestUtils.pretty_url(flow.request)).toEqual(
|
||||||
"http://foo:4444/bar"
|
"http://address:22/path"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -63,10 +63,10 @@ describe('updateStoreFromUrl', () => {
|
|||||||
|
|
||||||
describe('updateUrlFromStore', () => {
|
describe('updateUrlFromStore', () => {
|
||||||
let initialState = {
|
let initialState = {
|
||||||
flows: reduceFlows(undefined, {}),
|
flows: reduceFlows(undefined, {type: "other"}),
|
||||||
ui: reduceUI(undefined, {}),
|
ui: reduceUI(undefined, {type: "other"}),
|
||||||
eventLog: reduceEventLog(undefined, {}),
|
eventLog: reduceEventLog(undefined, {type: "other"}),
|
||||||
commandBar: reduceCommandBar(undefined, {}),
|
commandBar: reduceCommandBar(undefined, {type: "other"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should update initial url', () => {
|
it('should update initial url', () => {
|
||||||
@ -76,7 +76,7 @@ describe('updateUrlFromStore', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should update url', () => {
|
it('should update url', () => {
|
||||||
let flows = reduceFlows(undefined, flowsActions.select(123)),
|
let flows = reduceFlows(undefined, flowsActions.select("123")),
|
||||||
state = {
|
state = {
|
||||||
...initialState,
|
...initialState,
|
||||||
flows: reduceFlows(flows, flowsActions.setFilter('~u foo'))
|
flows: reduceFlows(flows, flowsActions.setFilter('~u foo'))
|
||||||
@ -89,10 +89,10 @@ describe('updateUrlFromStore', () => {
|
|||||||
|
|
||||||
describe('initialize', () => {
|
describe('initialize', () => {
|
||||||
let initialState = {
|
let initialState = {
|
||||||
flows: reduceFlows(undefined, {}),
|
flows: reduceFlows(undefined, {type: "other"}),
|
||||||
ui: reduceUI(undefined, {}),
|
ui: reduceUI(undefined, {type: "other"}),
|
||||||
eventLog: reduceEventLog(undefined, {}),
|
eventLog: reduceEventLog(undefined, {type: "other"}),
|
||||||
commandBar: reduceCommandBar(undefined, {}),
|
commandBar: reduceCommandBar(undefined, {type: "other"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should handle initial state', () => {
|
it('should handle initial state', () => {
|
@ -1,6 +1,7 @@
|
|||||||
import * as utils from '../utils'
|
import * as utils from '../utils'
|
||||||
|
import {enableFetchMocks} from "jest-fetch-mock";
|
||||||
|
|
||||||
global.fetch = jest.fn()
|
enableFetchMocks();
|
||||||
|
|
||||||
describe('formatSize', () => {
|
describe('formatSize', () => {
|
||||||
it('should return 0 when 0 byte', () => {
|
it('should return 0 when 0 byte', () => {
|
||||||
@ -19,9 +20,10 @@ describe('formatTimeDelta', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('formatTimeSTamp', () => {
|
describe('formatTimeStamp', () => {
|
||||||
it('should return formatted time', () => {
|
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', () => {
|
describe('fetchApi', () => {
|
||||||
it('should handle fetch operation', () => {
|
it('should handle fetch operation', () => {
|
||||||
utils.fetchApi('http://foo/bar', {method: "POST"})
|
utils.fetchApi('http://foo/bar', {method: "POST"})
|
||||||
expect(fetch.mock.calls[0][0]).toEqual(
|
expect(fetchMock.mock.calls[0][0]).toEqual(
|
||||||
"http://foo/bar"
|
"http://foo/bar"
|
||||||
)
|
)
|
||||||
fetch.mockClear()
|
fetchMock.mockClear()
|
||||||
|
|
||||||
utils.fetchApi('http://foo?bar=1', {method: "POST"})
|
utils.fetchApi('http://foo?bar=1', {method: "POST"})
|
||||||
expect(fetch.mock.calls[0][0]).toEqual(
|
expect(fetchMock.mock.calls[0][0]).toEqual(
|
||||||
"http://foo?bar=1"
|
"http://foo?bar=1"
|
||||||
)
|
)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be possible to do put request', () => {
|
it('should be possible to do put request', () => {
|
||||||
fetch.mockClear()
|
fetchMock.mockClear()
|
||||||
utils.fetchApi.put("http://foo", [1, 2, 3], {})
|
utils.fetchApi.put("http://foo", [1, 2, 3], {})
|
||||||
expect(fetch.mock.calls[0]).toEqual(
|
expect(fetchMock.mock.calls[0]).toEqual(
|
||||||
[
|
[
|
||||||
"http://foo",
|
"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})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,10 +5,21 @@
|
|||||||
*/
|
*/
|
||||||
import {fetchApi} from "../utils"
|
import {fetchApi} from "../utils"
|
||||||
import * as connectionActions from "../ducks/connection"
|
import * as connectionActions from "../ducks/connection"
|
||||||
|
import {Store} from "redux";
|
||||||
|
import {RootState} from "../ducks";
|
||||||
|
|
||||||
const CMD_RESET = 'reset'
|
const CMD_RESET = 'reset'
|
||||||
|
|
||||||
export default class WebsocketBackend {
|
export default class WebsocketBackend {
|
||||||
|
|
||||||
|
activeFetches: {
|
||||||
|
flows?: []
|
||||||
|
events?: []
|
||||||
|
options?: []
|
||||||
|
}
|
||||||
|
store: Store<RootState>
|
||||||
|
socket: WebSocket
|
||||||
|
|
||||||
constructor(store) {
|
constructor(store) {
|
||||||
this.activeFetches = {}
|
this.activeFetches = {}
|
||||||
this.store = store
|
this.store = store
|
||||||
@ -75,7 +86,7 @@ export default class WebsocketBackend {
|
|||||||
console.error("websocket connection closed", closeEvent)
|
console.error("websocket connection closed", closeEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
onError() {
|
onError(error) {
|
||||||
// FIXME
|
// FIXME
|
||||||
console.error("websocket connection errored", arguments)
|
console.error("websocket connection errored", arguments)
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import React, {useEffect, useRef, useState} from 'react'
|
import React, {useEffect, useRef, useState} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import {fetchApi, Key, runCommand} from '../utils'
|
import {fetchApi, runCommand} from '../utils'
|
||||||
import Filt from '../filt/command'
|
import Filt from '../filt/command'
|
||||||
|
|
||||||
type CommandParameter = {
|
type CommandParameter = {
|
||||||
@ -151,7 +151,7 @@ export default function CommandBar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onKeyDown = (e) => {
|
const onKeyDown = (e) => {
|
||||||
if (e.keyCode === Key.ENTER) {
|
if (e.key === "Enter") {
|
||||||
const [cmd, ...args] = Filt.parse(input);
|
const [cmd, ...args] = Filt.parse(input);
|
||||||
|
|
||||||
setHistory([...history, input]);
|
setHistory([...history, input]);
|
||||||
@ -184,7 +184,7 @@ export default function CommandBar() {
|
|||||||
setCurrentCompletion(0)
|
setCurrentCompletion(0)
|
||||||
setCompletionCandidate(availableCommands)
|
setCompletionCandidate(availableCommands)
|
||||||
}
|
}
|
||||||
if (e.keyCode === Key.UP) {
|
if (e.key === "ArrowUp") {
|
||||||
let nextPos;
|
let nextPos;
|
||||||
if (currentPos === undefined) {
|
if (currentPos === undefined) {
|
||||||
nextPos = history.length - 1;
|
nextPos = history.length - 1;
|
||||||
@ -195,7 +195,7 @@ export default function CommandBar() {
|
|||||||
setOriginalInput(history[nextPos])
|
setOriginalInput(history[nextPos])
|
||||||
setCurrentPos(nextPos)
|
setCurrentPos(nextPos)
|
||||||
}
|
}
|
||||||
if (e.keyCode === Key.DOWN) {
|
if (e.key === "ArrowDown") {
|
||||||
if (currentPos === undefined) {
|
if (currentPos === undefined) {
|
||||||
return
|
return
|
||||||
} else if (currentPos == history.length - 1) {
|
} else if (currentPos == history.length - 1) {
|
||||||
@ -209,7 +209,7 @@ export default function CommandBar() {
|
|||||||
setCurrentPos(nextPos)
|
setCurrentPos(nextPos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.keyCode === Key.TAB) {
|
if (e.key === "Tab") {
|
||||||
setInput(completionCandidate[currentCompletion])
|
setInput(completionCandidate[currentCompletion])
|
||||||
setCurrentCompletion((currentCompletion + 1) % completionCandidate.length)
|
setCurrentCompletion((currentCompletion + 1) % completionCandidate.length)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -3,9 +3,19 @@ import PropTypes from 'prop-types'
|
|||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import shallowEqual from 'shallowequal'
|
import shallowEqual from 'shallowequal'
|
||||||
import AutoScroll from '../helpers/AutoScroll'
|
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 = {
|
static propTypes = {
|
||||||
events: PropTypes.array.isRequired,
|
events: PropTypes.array.isRequired,
|
||||||
@ -16,6 +26,8 @@ class EventLogList extends Component {
|
|||||||
rowHeight: 18,
|
rowHeight: 18,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
heights: {[id: string]: number}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@ -70,14 +82,14 @@ class EventLogList extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<pre onScroll={this.onViewportUpdate}>
|
<pre onScroll={this.onViewportUpdate}>
|
||||||
<div style={{ height: vScroll.paddingTop }}></div>
|
<div style={{ height: vScroll.paddingTop }}/>
|
||||||
{events.slice(vScroll.start, vScroll.end).map(event => (
|
{events.slice(vScroll.start, vScroll.end).map(event => (
|
||||||
<div key={event.id} ref={node => this.setHeight(event.id, node)}>
|
<div key={event.id} ref={node => this.setHeight(event.id, node)}>
|
||||||
<LogIcon event={event}/>
|
<LogIcon event={event}/>
|
||||||
{event.message}
|
{event.message}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div style={{ height: vScroll.paddingBottom }}></div>
|
<div style={{ height: vScroll.paddingBottom }}/>
|
||||||
</pre>
|
</pre>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -90,7 +102,7 @@ function LogIcon({ event }) {
|
|||||||
warn: 'exclamation-triangle',
|
warn: 'exclamation-triangle',
|
||||||
error: 'ban'
|
error: 'ban'
|
||||||
}[event.level] || 'info'
|
}[event.level] || 'info'
|
||||||
return <i className={`fa fa-fw fa-${icon}`}></i>
|
return <i className={`fa fa-fw fa-${icon}`}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AutoScroll(EventLogList)
|
export default AutoScroll(EventLogList)
|
@ -1,7 +1,6 @@
|
|||||||
import React, {Component} from 'react'
|
import React, {Component} from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import {Key} from '../../utils'
|
|
||||||
import Filt from '../../filt/filt'
|
import Filt from '../../filt/filt'
|
||||||
import FilterDocs from './FilterDocs'
|
import FilterDocs from './FilterDocs'
|
||||||
|
|
||||||
@ -91,7 +90,7 @@ export default class FilterInput extends Component<FilterInputProps, FilterInput
|
|||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(e) {
|
onKeyDown(e) {
|
||||||
if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) {
|
if (e.key === "Escape" || e.key === "Enter") {
|
||||||
this.blur()
|
this.blur()
|
||||||
// If closed using ESC/ENTER, hide the tooltip.
|
// If closed using ESC/ENTER, hide the tooltip.
|
||||||
this.setState({mousefocus: false})
|
this.setState({mousefocus: false})
|
||||||
|
@ -2,11 +2,10 @@ import React, { Component } from "react"
|
|||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import {connect} from "react-redux"
|
import {connect} from "react-redux"
|
||||||
import {update as updateOptions} from "../../ducks/options"
|
import {update as updateOptions} from "../../ducks/options"
|
||||||
import { Key } from "../../utils"
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
const stopPropagation = e => {
|
const stopPropagation = e => {
|
||||||
if (e.keyCode !== Key.ESC) {
|
if (e.key !== "Escape") {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15,6 +14,7 @@ BooleanOption.propTypes = {
|
|||||||
value: PropTypes.bool.isRequired,
|
value: PropTypes.bool.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
function BooleanOption({value, onChange, ...props}) {
|
function BooleanOption({value, onChange, ...props}) {
|
||||||
return (
|
return (
|
||||||
<div className="checkbox">
|
<div className="checkbox">
|
||||||
@ -34,6 +34,7 @@ StringOption.propTypes = {
|
|||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
function StringOption({value, onChange, ...props}) {
|
function StringOption({value, onChange, ...props}) {
|
||||||
return (
|
return (
|
||||||
<input type="text"
|
<input type="text"
|
||||||
@ -43,6 +44,7 @@ function StringOption({ value, onChange, ...props }) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Optional(Component) {
|
function Optional(Component) {
|
||||||
return function ({onChange, ...props}) {
|
return function ({onChange, ...props}) {
|
||||||
return <Component
|
return <Component
|
||||||
@ -56,6 +58,7 @@ NumberOption.propTypes = {
|
|||||||
value: PropTypes.number.isRequired,
|
value: PropTypes.number.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
function NumberOption({value, onChange, ...props}) {
|
function NumberOption({value, onChange, ...props}) {
|
||||||
return (
|
return (
|
||||||
<input type="number"
|
<input type="number"
|
||||||
@ -70,6 +73,7 @@ ChoicesOption.propTypes = {
|
|||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChoicesOption({value, onChange, choices, ...props}) {
|
export function ChoicesOption({value, onChange, choices, ...props}) {
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
@ -90,6 +94,7 @@ StringSequenceOption.propTypes = {
|
|||||||
value: PropTypes.arrayOf(PropTypes.string).isRequired,
|
value: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
function StringSequenceOption({value, onChange, ...props}) {
|
function StringSequenceOption({value, onChange, ...props}) {
|
||||||
const height = Math.max(value.length, 1)
|
const height = Math.max(value.length, 1)
|
||||||
return <textarea
|
return <textarea
|
||||||
@ -132,6 +137,7 @@ function PureOption({ choices, type, value, onChange, name, error }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
(state, {name}) => ({
|
(state, {name}) => ({
|
||||||
...state.options_meta[name],
|
...state.options_meta[name],
|
||||||
|
@ -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 React, {Component} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import {Key} from '../../utils'
|
|
||||||
|
|
||||||
export interface ValueEditorProps {
|
export interface ValueEditorProps {
|
||||||
content: string
|
content: string
|
||||||
onEditDone: (newVal: string) => void
|
onEditDone: (newVal: string) => void
|
||||||
@ -157,13 +155,13 @@ export default class ValueEditor extends Component<ValueEditorProps> {
|
|||||||
onKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
|
onKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
|
||||||
EVENT_DEBUG && console.debug("keydown", e)
|
EVENT_DEBUG && console.debug("keydown", e)
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
switch (e.keyCode) {
|
switch (e.key) {
|
||||||
case Key.ESC:
|
case "Escape":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.resetValue()
|
this.resetValue()
|
||||||
this.finishEditing()
|
this.finishEditing()
|
||||||
break
|
break
|
||||||
case Key.ENTER:
|
case "Enter":
|
||||||
if (!e.shiftKey) {
|
if (!e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.finishEditing()
|
this.finishEditing()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
const symShouldStick = Symbol("shouldStick");
|
const symShouldStick = Symbol("shouldStick") as any;
|
||||||
const isAtBottom = v => v.scrollTop + v.clientHeight === v.scrollHeight;
|
const isAtBottom = v => v.scrollTop + v.clientHeight === v.scrollHeight;
|
||||||
|
|
||||||
export default Component => Object.assign(class AutoScrollWrapper extends Component {
|
export default Component => Object.assign(class AutoScrollWrapper extends Component {
|
@ -18,7 +18,23 @@
|
|||||||
* - {number} paddingTop
|
* - {number} paddingTop
|
||||||
* - {number} paddingBottom
|
* - {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) {
|
if (!opts) {
|
||||||
return { start: 0, end: 0, paddingTop: 0, paddingBottom: 0 };
|
return { start: 0, end: 0, paddingTop: 0, paddingBottom: 0 };
|
||||||
}
|
}
|
@ -1,5 +1,3 @@
|
|||||||
import {Reducer} from "redux";
|
|
||||||
|
|
||||||
export const TOGGLE_VISIBILITY = 'COMMANDBAR_TOGGLE_VISIBILITY'
|
export const TOGGLE_VISIBILITY = 'COMMANDBAR_TOGGLE_VISIBILITY'
|
||||||
|
|
||||||
interface CommandBarState {
|
interface CommandBarState {
|
||||||
@ -10,7 +8,7 @@ export const defaultState: CommandBarState = {
|
|||||||
visible: false,
|
visible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer: Reducer<CommandBarState> = (state = defaultState, action): CommandBarState => {
|
export default function reducer(state = defaultState, action): CommandBarState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case TOGGLE_VISIBILITY:
|
case TOGGLE_VISIBILITY:
|
||||||
return {
|
return {
|
||||||
@ -22,7 +20,6 @@ const reducer: Reducer<CommandBarState> = (state = defaultState, action): Comman
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default reducer
|
|
||||||
|
|
||||||
export function toggleVisibility() {
|
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,
|
* Conf houses properties about the current mitmproxy instance that are not options,
|
||||||
* e.g. the list of available content views or the current version.
|
* e.g. the list of available content views or the current version.
|
||||||
*/
|
*/
|
||||||
import {Reducer} from "redux";
|
|
||||||
|
|
||||||
interface ConfState {
|
interface ConfState {
|
||||||
static: boolean
|
static: boolean
|
||||||
@ -17,7 +16,6 @@ export const defaultState: ConfState = window.MITMWEB_CONF || {
|
|||||||
contentViews: ["Auto", "Raw"],
|
contentViews: ["Auto", "Raw"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer: Reducer<ConfState> = (state = defaultState, action): ConfState => {
|
export default function reducer(state = defaultState, action): ConfState {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
export default reducer
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import {Reducer} from "redux";
|
|
||||||
|
|
||||||
export enum ConnectionState {
|
export enum ConnectionState {
|
||||||
INIT = "CONNECTION_INIT",
|
INIT = "CONNECTION_INIT",
|
||||||
FETCHING = "CONNECTION_FETCHING", // WebSocket is established, but still fetching resources.
|
FETCHING = "CONNECTION_FETCHING", // WebSocket is established, but still fetching resources.
|
||||||
@ -19,7 +17,7 @@ const defaultState: ConnState = {
|
|||||||
message: undefined,
|
message: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer: Reducer<ConnState> = (state = defaultState, action) => {
|
export default function reducer(state = defaultState, action): ConnState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ConnectionState.ESTABLISHED:
|
case ConnectionState.ESTABLISHED:
|
||||||
case ConnectionState.FETCHING:
|
case ConnectionState.FETCHING:
|
||||||
@ -34,7 +32,6 @@ const reducer: Reducer<ConnState> = (state = defaultState, action) => {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default reducer
|
|
||||||
|
|
||||||
export function startFetching() {
|
export function startFetching() {
|
||||||
return {type: ConnectionState.FETCHING}
|
return {type: ConnectionState.FETCHING}
|
||||||
|
@ -7,7 +7,7 @@ export const TOGGLE_FILTER = 'EVENTS_TOGGLE_FILTER'
|
|||||||
|
|
||||||
type LogLevel = 'debug' | 'info' | 'web' | 'warn' | 'error';
|
type LogLevel = 'debug' | 'info' | 'web' | 'warn' | 'error';
|
||||||
|
|
||||||
interface EventLogItem extends store.Item {
|
export interface EventLogItem extends store.Item {
|
||||||
message: string
|
message: string
|
||||||
level: LogLevel
|
level: LogLevel
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export interface FlowsState extends store.State<Flow> {
|
|||||||
selected: string[],
|
selected: string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: FlowsState = {
|
export const defaultState: FlowsState = {
|
||||||
highlight: undefined,
|
highlight: undefined,
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
sort: {column: undefined, desc: false},
|
sort: {column: undefined, desc: false},
|
||||||
@ -36,7 +36,7 @@ const defaultState: FlowsState = {
|
|||||||
...store.defaultState
|
...store.defaultState
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function reduce(state: FlowsState = defaultState, action): FlowsState {
|
export default function reducer(state: FlowsState = defaultState, action): FlowsState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case ADD:
|
case ADD:
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import {fetchApi} from "../utils"
|
import {fetchApi} from "../utils"
|
||||||
import * as optionsEditorActions from "./ui/optionsEditor"
|
import * as optionsEditorActions from "./ui/optionsEditor"
|
||||||
import _ from "lodash"
|
|
||||||
import {Reducer} from "redux";
|
|
||||||
import {defaultState, Option, OptionsState} from "./_options_gen";
|
import {defaultState, Option, OptionsState} from "./_options_gen";
|
||||||
import {AppThunk} from "./index";
|
import {AppThunk} from "./index";
|
||||||
|
|
||||||
@ -11,8 +9,7 @@ export const REQUEST_UPDATE = 'REQUEST_UPDATE'
|
|||||||
|
|
||||||
export {Option, defaultState}
|
export {Option, defaultState}
|
||||||
|
|
||||||
const reducer: Reducer<OptionsState> = (state = defaultState, action) => {
|
export default function reducer(state = defaultState, action): OptionsState {
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case RECEIVE:
|
case RECEIVE:
|
||||||
let s = <OptionsState>{};
|
let s = <OptionsState>{};
|
||||||
@ -34,7 +31,6 @@ const reducer: Reducer<OptionsState> = (state = defaultState, action) => {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default reducer
|
|
||||||
|
|
||||||
export async function pureSendUpdate(option: Option, value, dispatch) {
|
export async function pureSendUpdate(option: Option, value, dispatch) {
|
||||||
try {
|
try {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import {Reducer} from "redux";
|
|
||||||
|
|
||||||
export const
|
export const
|
||||||
SET_TAB = "UI_FLOWVIEW_SET_TAB",
|
SET_TAB = "UI_FLOWVIEW_SET_TAB",
|
||||||
SET_CONTENT_VIEW_FOR = "SET_CONTENT_VIEW_FOR"
|
SET_CONTENT_VIEW_FOR = "SET_CONTENT_VIEW_FOR"
|
||||||
@ -15,8 +13,7 @@ export const defaultState: UiFlowState = {
|
|||||||
contentViewFor: {},
|
contentViewFor: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer: Reducer<UiFlowState> = (state = defaultState, action): UiFlowState => {
|
export default function reducer(state = defaultState, action): UiFlowState {
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case SET_CONTENT_VIEW_FOR:
|
case SET_CONTENT_VIEW_FOR:
|
||||||
@ -38,7 +35,6 @@ const reducer: Reducer<UiFlowState> = (state = defaultState, action): UiFlowStat
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default reducer;
|
|
||||||
|
|
||||||
export function selectTab(tab) {
|
export function selectTab(tab) {
|
||||||
return {type: SET_TAB, tab}
|
return {type: SET_TAB, tab}
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import { Key } from "../../utils"
|
|
||||||
import {selectTab} from "./flow"
|
import {selectTab} from "./flow"
|
||||||
import * as flowsActions from "../flows"
|
import * as flowsActions from "../flows"
|
||||||
import * as modalActions from "./modal"
|
import * as modalActions from "./modal"
|
||||||
import {tabsForFlow} from "../../components/FlowView";
|
import {tabsForFlow} from "../../components/FlowView";
|
||||||
|
|
||||||
|
|
||||||
export function onKeyDown(e) {
|
export function onKeyDown(e: KeyboardEvent) {
|
||||||
//console.debug("onKeyDown", e)
|
//console.debug("onKeyDown", e)
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
return () => {
|
return () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let key = e.keyCode,
|
const key = e.key;
|
||||||
shiftKey = e.shiftKey
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
|
||||||
@ -20,42 +18,42 @@ export function onKeyDown(e) {
|
|||||||
flow = flows.byId[getState().flows.selected[0]]
|
flow = flows.byId[getState().flows.selected[0]]
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Key.K:
|
case "k":
|
||||||
case Key.UP:
|
case "ArrowUp":
|
||||||
dispatch(flowsActions.selectRelative(flows, -1))
|
dispatch(flowsActions.selectRelative(flows, -1))
|
||||||
break
|
break
|
||||||
|
|
||||||
case Key.J:
|
case "j":
|
||||||
case Key.DOWN:
|
case "ArrowDown":
|
||||||
dispatch(flowsActions.selectRelative(flows, +1))
|
dispatch(flowsActions.selectRelative(flows, +1))
|
||||||
break
|
break
|
||||||
|
|
||||||
case Key.SPACE:
|
case " ":
|
||||||
case Key.PAGE_DOWN:
|
case "PageDown":
|
||||||
dispatch(flowsActions.selectRelative(flows, +10))
|
dispatch(flowsActions.selectRelative(flows, +10))
|
||||||
break
|
break
|
||||||
|
|
||||||
case Key.PAGE_UP:
|
case "PageUp":
|
||||||
dispatch(flowsActions.selectRelative(flows, -10))
|
dispatch(flowsActions.selectRelative(flows, -10))
|
||||||
break
|
break
|
||||||
|
|
||||||
case Key.END:
|
case "End":
|
||||||
dispatch(flowsActions.selectRelative(flows, +1e10))
|
dispatch(flowsActions.selectRelative(flows, +1e10))
|
||||||
break
|
break
|
||||||
|
|
||||||
case Key.HOME:
|
case "Home":
|
||||||
dispatch(flowsActions.selectRelative(flows, -1e10))
|
dispatch(flowsActions.selectRelative(flows, -1e10))
|
||||||
break
|
break
|
||||||
|
|
||||||
case Key.ESC:
|
case "Escape":
|
||||||
if (getState().ui.modal.activeModal) {
|
if (getState().ui.modal.activeModal) {
|
||||||
dispatch(modalActions.hideModal())
|
dispatch(modalActions.hideModal())
|
||||||
} else {
|
} else {
|
||||||
dispatch(flowsActions.select(null))
|
dispatch(flowsActions.select(undefined))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case Key.LEFT: {
|
case "ArrowLeft": {
|
||||||
if (!flow) break
|
if (!flow) break
|
||||||
let tabs = tabsForFlow(flow),
|
let tabs = tabsForFlow(flow),
|
||||||
currentTab = getState().ui.flow.tab,
|
currentTab = getState().ui.flow.tab,
|
||||||
@ -64,8 +62,8 @@ export function onKeyDown(e) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case Key.TAB:
|
case "Tab":
|
||||||
case Key.RIGHT: {
|
case "ArrowRight": {
|
||||||
if (!flow) break
|
if (!flow) break
|
||||||
let tabs = tabsForFlow(flow),
|
let tabs = tabsForFlow(flow),
|
||||||
currentTab = getState().ui.flow.tab,
|
currentTab = getState().ui.flow.tab,
|
||||||
@ -74,57 +72,62 @@ export function onKeyDown(e) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case Key.D: {
|
case "d": {
|
||||||
if (!flow) {
|
if (!flow) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (shiftKey) {
|
|
||||||
dispatch(flowsActions.duplicate(flow))
|
|
||||||
} else {
|
|
||||||
dispatch(flowsActions.remove(flow))
|
dispatch(flowsActions.remove(flow))
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case Key.A: {
|
case "D": {
|
||||||
if (shiftKey) {
|
if (!flow) {
|
||||||
dispatch(flowsActions.resumeAll())
|
return
|
||||||
} else if (flow && flow.intercepted) {
|
}
|
||||||
|
dispatch(flowsActions.duplicate(flow))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "a": {
|
||||||
|
if (flow && flow.intercepted) {
|
||||||
dispatch(flowsActions.resume(flow))
|
dispatch(flowsActions.resume(flow))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case "A": {
|
||||||
|
dispatch(flowsActions.resumeAll())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case Key.R: {
|
case "r": {
|
||||||
if (!shiftKey && flow) {
|
if (flow) {
|
||||||
dispatch(flowsActions.replay(flow))
|
dispatch(flowsActions.replay(flow))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case Key.V: {
|
case "v": {
|
||||||
if (!shiftKey && flow && flow.modified) {
|
if (flow && flow.modified) {
|
||||||
dispatch(flowsActions.revert(flow))
|
dispatch(flowsActions.revert(flow))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case Key.X: {
|
case "x": {
|
||||||
if (shiftKey) {
|
if (flow && flow.intercepted) {
|
||||||
dispatch(flowsActions.killAll())
|
|
||||||
} else if (flow && flow.intercepted) {
|
|
||||||
dispatch(flowsActions.kill(flow))
|
dispatch(flowsActions.kill(flow))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case Key.Z: {
|
case "X": {
|
||||||
if (!shiftKey) {
|
dispatch(flowsActions.killAll())
|
||||||
dispatch(flowsActions.clear())
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "z": {
|
||||||
|
dispatch(flowsActions.clear())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return
|
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
|
let {byId, list, listIndex, view, viewIndex} = state
|
||||||
|
|
||||||
|
@ -5,28 +5,6 @@ import * as React from 'react'
|
|||||||
window._ = _;
|
window._ = _;
|
||||||
window.React = React;
|
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) {
|
export var formatSize = function (bytes) {
|
||||||
if (bytes === 0)
|
if (bytes === 0)
|
||||||
return "0";
|
return "0";
|
||||||
|
Loading…
Reference in New Issue
Block a user