diff --git a/web/src/js/__tests__/components/ContentView/ContentViewSpec.js b/web/src/js/__tests__/components/ContentView/ContentViewSpec.js index 281b57557..9b6f8a66b 100644 --- a/web/src/js/__tests__/components/ContentView/ContentViewSpec.js +++ b/web/src/js/__tests__/components/ContentView/ContentViewSpec.js @@ -19,16 +19,12 @@ describe('ViewImage Component', () => { }) describe('ViewServer Component', () => { - let store = TStore(), - setContentViewDescFn = jest.fn(), - setContentFn = jest.fn() + let store = TStore() it('should render correctly and connect to state', () => { let provider = renderer.create( @@ -37,38 +33,17 @@ describe('ViewServer Component', () => { expect(tree).toMatchSnapshot() let viewServer = renderer.create( - + + + ) tree = viewServer.toJSON() expect(tree).toMatchSnapshot() }) - - it('should handle componentWillReceiveProps', () => { - // case of fail to parse content - let viewServer = TestUtils.renderIntoDocument( - - ) - viewServer.UNSAFE_componentWillReceiveProps({...viewServer.props, content: '{foo' }) - let e = '' - try {JSON.parse('{foo') } catch(err){ e = err.message} - expect(viewServer.data).toEqual({ description: e, lines: [] }) - }) }) describe('Edit Component', () => { diff --git a/web/src/js/__tests__/components/ContentView/ShowFullContentButtonSpec.js b/web/src/js/__tests__/components/ContentView/ShowFullContentButtonSpec.js index 14871f130..56656c7a6 100644 --- a/web/src/js/__tests__/components/ContentView/ShowFullContentButtonSpec.js +++ b/web/src/js/__tests__/components/ContentView/ShowFullContentButtonSpec.js @@ -1,39 +1,22 @@ import React from 'react' import renderer from 'react-test-renderer' import { Provider } from 'react-redux' -import ConnectedComponent, { ShowFullContentButton } from '../../../components/ContentView/ShowFullContentButton' +import ShowFullContentButton from '../../../components/ContentView/ShowFullContentButton' import { TStore } from '../../ducks/tutils' describe('ShowFullContentButton Component', () => { + let store = TStore() + let setShowFullContentFn = jest.fn(), showFullContentButton = renderer.create( - + + + ), tree = showFullContentButton.toJSON() it('should render correctly', () => { expect(tree).toMatchSnapshot() }) - - it('should handle click', () => { - tree.children[0].props.onClick() - expect(setShowFullContentFn).toBeCalled() - }) - - it('should connect to state', () => { - let store = TStore(), - provider = renderer.create( - - - - ), - tree = provider.toJSON() - expect(tree).toMatchSnapshot() - }) }) diff --git a/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewOptionsSpec.js.snap b/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewOptionsSpec.js.snap index 1093343d8..57368de2b 100644 --- a/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewOptionsSpec.js.snap +++ b/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewOptionsSpec.js.snap @@ -4,23 +4,12 @@ exports[`ContentViewOptions Component should render correctly 1`] = `
- - - - View: - - - auto - - - - + + + View: + + edit +     + + + +   - - foo -
`; diff --git a/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewSpec.js.snap b/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewSpec.js.snap index ff651081c..7415725f6 100644 --- a/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewSpec.js.snap +++ b/web/src/js/__tests__/components/ContentView/__snapshots__/ContentViewSpec.js.snap @@ -34,19 +34,6 @@ exports[`ViewServer Component should render correctly and connect to state 1`] = exports[`ViewServer Component should render correctly and connect to state 2`] = `
-
-    
- - 1 - - - 1 - -
-
+
 
`; diff --git a/web/src/js/__tests__/components/ContentView/__snapshots__/ShowFullContentButtonSpec.js.snap b/web/src/js/__tests__/components/ContentView/__snapshots__/ShowFullContentButtonSpec.js.snap index bc93705ac..504f4a231 100644 --- a/web/src/js/__tests__/components/ContentView/__snapshots__/ShowFullContentButtonSpec.js.snap +++ b/web/src/js/__tests__/components/ContentView/__snapshots__/ShowFullContentButtonSpec.js.snap @@ -1,23 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ShowFullContentButton Component should connect to state 1`] = `null`; - -exports[`ShowFullContentButton Component should render correctly 1`] = ` -
- - - - 10 - / - 20 - are visible   - -
-`; +exports[`ShowFullContentButton Component should render correctly 1`] = `null`; diff --git a/web/src/js/__tests__/components/FlowView/MessagesSpec.js b/web/src/js/__tests__/components/FlowView/MessagesSpec.js index 79b9a9a41..20a1124ea 100644 --- a/web/src/js/__tests__/components/FlowView/MessagesSpec.js +++ b/web/src/js/__tests__/components/FlowView/MessagesSpec.js @@ -71,9 +71,7 @@ describe('Request Component', () => { }) it('should handle uploadContent on flow request ContentViewOptions', () => { - // The line below shouldn't have .type, this is a workaround for https://github.com/facebook/react/issues/17301. - // If this test breaks, just remove it. - let contentViewOptions = provider.root.findByType(ContentViewOptions.type) + let contentViewOptions = provider.root.findByType(ContentViewOptions) contentViewOptions.props.uploadContent('foo') expect(fetch).toBeCalled() fetch.mockClear() @@ -132,9 +130,7 @@ describe('Response Component', () => { }) it('should handle updateContent on flow response ContentViewOptions', () => { - // The line below shouldn't have .type, this is a workaround for https://github.com/facebook/react/issues/17301. - // If this test breaks, just remove it. - let contentViewOptions = provider.root.findByType(ContentViewOptions.type) + let contentViewOptions = provider.root.findByType(ContentViewOptions) contentViewOptions.props.uploadContent('foo') expect(fetch).toBeCalled() fetch.mockClear() diff --git a/web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap b/web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap index f794a4fa6..2bebe7084 100644 --- a/web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap +++ b/web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap @@ -196,23 +196,12 @@ exports[`Request Component should render correctly 1`] = `
- - - - View: - - - auto - - - - + + + View: + + edit +     + + + +   - - foo -
@@ -456,23 +457,12 @@ exports[`Response Component should render correctly 1`] = `
- - - - View: - - - auto - - - - + + + View: + + edit +     + + + +   - - foo -
diff --git a/web/src/js/components/CommandBar.tsx b/web/src/js/components/CommandBar.tsx index a4ccda8fb..df5490816 100644 --- a/web/src/js/components/CommandBar.tsx +++ b/web/src/js/components/CommandBar.tsx @@ -21,7 +21,7 @@ type ResultProps = { results: CommandResult[], } -function getAvailableCommands(commands, input: string = "") { +function getAvailableCommands(commands: object, input: string = "") { if (!commands) return [] let availableCommands: string[] = [] for (const [command, args] of Object.entries(commands)) { @@ -37,7 +37,7 @@ export function Results({results}: ResultProps) { useEffect(() => { if (resultElement) { - resultElement.current?.addEventListener('DOMNodeInserted', event => { + resultElement.current.addEventListener('DOMNodeInserted', event => { const { currentTarget: target } = event; target.scroll({ top: target.scrollHeight, behavior: 'auto' }); }); @@ -83,7 +83,7 @@ export default function CommandBar() { const [completionCandidate, setCompletionCandidate] = useState([]) const [availableCommands, setAvailableCommands] = useState([]) - const [allCommands, setAllCommands] = useState({}) + const [allCommands, setAllCommands] = useState({}) const [nextArgs, setNextArgs] = useState([]) const [currentArg, setCurrentArg] = useState(0) const [signatureHelp, setSignatureHelp] = useState("") diff --git a/web/src/js/components/ContentView/ContentLoader.jsx b/web/src/js/components/ContentView/ContentLoader.tsx similarity index 84% rename from web/src/js/components/ContentView/ContentLoader.jsx rename to web/src/js/components/ContentView/ContentLoader.tsx index aadf1ef90..4d6ce63a4 100644 --- a/web/src/js/components/ContentView/ContentLoader.jsx +++ b/web/src/js/components/ContentView/ContentLoader.tsx @@ -1,19 +1,26 @@ import React, { Component } from 'react' -import PropTypes from 'prop-types' import { MessageUtils } from '../../flow/utils' +type ContentLoaderProps = { + content: string, + contentView: object, + flow: object, + message: { + content: string, + contentHash: string, + }, +} + +type ContentLoaderStates = { + content: string | undefined, + request: { abort: () => void }| undefined, +} + export default function withContentLoader(View) { - return class extends React.Component { - static displayName = View.displayName || View.name - static matches = View.matches - - static propTypes = { - ...View.propTypes, - content: PropTypes.string, // mark as non-required - flow: PropTypes.object.isRequired, - message: PropTypes.object.isRequired, - } + return class extends React.Component { + static displayName: string = View.displayName || View.name + static matches: (message: any) => boolean = View.matches constructor(props) { super(props) @@ -45,6 +52,7 @@ export default function withContentLoader(View) { updateContent(props) { if (this.state.request) { + console.log("request:",this.state.request) this.state.request.abort() } // We have a few special cases where we do not need to make an HTTP request. diff --git a/web/src/js/components/ContentView/ContentViewOptions.jsx b/web/src/js/components/ContentView/ContentViewOptions.jsx deleted file mode 100644 index e3cc39cd4..000000000 --- a/web/src/js/components/ContentView/ContentViewOptions.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import ViewSelector from './ViewSelector' -import UploadContentButton from './UploadContentButton' -import DownloadContentButton from './DownloadContentButton' - -ContentViewOptions.propTypes = { - flow: PropTypes.object.isRequired, - message: PropTypes.object.isRequired, -} - -function ContentViewOptions({ flow, message, uploadContent, readonly, contentViewDescription }) { - return ( -
- {readonly ? : View: edit} -   - -   - {!readonly && } -   - {readonly && {contentViewDescription}} -
- ) -} - -export default connect( - state => ({ - contentViewDescription: state.ui.flow.viewDescription, - readonly: !state.ui.flow.modifiedFlow, - }) -)(ContentViewOptions) diff --git a/web/src/js/components/ContentView/ContentViewOptions.tsx b/web/src/js/components/ContentView/ContentViewOptions.tsx new file mode 100644 index 000000000..8a486833f --- /dev/null +++ b/web/src/js/components/ContentView/ContentViewOptions.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import ViewSelector from './ViewSelector' +import UploadContentButton from './UploadContentButton' +import DownloadContentButton from './DownloadContentButton' +import { useAppSelector } from "../../ducks"; + +type ContentViewOptionsProps = { + flow: object, + message: object, + uploadContent: () => void, +} + +export default function ContentViewOptions({ flow, message, uploadContent }: ContentViewOptionsProps) { + const contentViewDescription = useAppSelector(state => state.ui.flow.viewDescription) + const readonly = useAppSelector(state => state.ui.flow.modifiedFlow); + return ( +
+ {readonly ? : View: edit} +   + +   + {!readonly && } +   + {readonly && {contentViewDescription}} +
+ ) +} diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx deleted file mode 100644 index 78397fc3e..000000000 --- a/web/src/js/components/ContentView/ContentViews.jsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { setContentViewDescription, setContent } from '../../ducks/ui/flow' -import withContentLoader from './ContentLoader' -import { MessageUtils } from '../../flow/utils' -import CodeEditor from './CodeEditor' - - -const isImage = /^image\/(png|jpe?g|gif|webp|vnc.microsoft.icon|x-icon)$/i -ViewImage.matches = msg => isImage.test(MessageUtils.getContentType(msg)) -ViewImage.propTypes = { - flow: PropTypes.object.isRequired, - message: PropTypes.object.isRequired, -} -function ViewImage({ flow, message }) { - return ( -
- preview -
- ) -} - -Edit.propTypes = { - content: PropTypes.string.isRequired, -} - -function Edit({ content, onChange }) { - return -} -Edit = withContentLoader(Edit) - -export class PureViewServer extends Component { - static propTypes = { - showFullContent: PropTypes.bool.isRequired, - maxLines: PropTypes.number.isRequired, - setContentViewDescription : PropTypes.func.isRequired, - setContent: PropTypes.func.isRequired - } - - UNSAFE_componentWillMount(){ - this.setContentView(this.props) - } - - UNSAFE_componentWillReceiveProps(nextProps){ - if (nextProps.content !== this.props.content) { - this.setContentView(nextProps) - } - } - - setContentView(props){ - try { - this.data = JSON.parse(props.content) - }catch(err) { - this.data = {lines: [], description: err.message} - } - - props.setContentViewDescription(props.contentView !== this.data.description ? this.data.description : '') - props.setContent(this.data.lines) - } - - render() { - const {content, contentView, message, maxLines} = this.props - let lines = this.props.showFullContent ? this.data.lines : this.data.lines.slice(0, maxLines) - return ( -
- {ViewImage.matches(message) && } -
-                    {lines.map((line, i) =>
-                        
- {line.map((element, j) => { - let [style, text] = element - return ( - - {text} - - ) - })} -
- )} -
-
- ) - } - -} - -const ViewServer = connect( - state => ({ - showFullContent: state.ui.flow.showFullContent, - maxLines: state.ui.flow.maxContentLines - }), - { - setContentViewDescription, - setContent - } -)(withContentLoader(PureViewServer)) - -export { Edit, ViewServer, ViewImage } diff --git a/web/src/js/components/ContentView/ContentViews.tsx b/web/src/js/components/ContentView/ContentViews.tsx new file mode 100644 index 000000000..5a521240a --- /dev/null +++ b/web/src/js/components/ContentView/ContentViews.tsx @@ -0,0 +1,97 @@ +import React, { useEffect, useState } from 'react' +import { setContentViewDescription, setContent } from '../../ducks/ui/flow' +import withContentLoader from './ContentLoader' +import { MessageUtils } from '../../flow/utils' +import CodeEditor from './CodeEditor' +import { useAppDispatch, useAppSelector } from "../../ducks"; + + +const isImage = /^image\/(png|jpe?g|gif|webp|vnc.microsoft.icon|x-icon)$/i +ViewImage.matches = msg => isImage.test(MessageUtils.getContentType(msg)) + +type ViewImageProps = { + flow: object, + message: object, +} + +function ViewImage({ flow, message }: ViewImageProps) { + return ( +
+ preview +
+ ) +} + +type EditProps = { + content: string, + onChange: () => void, +} + +function PureEdit({ content, onChange }: EditProps) { + return +} +const Edit = withContentLoader(PureEdit) + +type PureViewServerProps = { + flow: object, + message: object, + content: string, +} + +type PureViewServerStates = { + lines: string[][], + description: string, +} + +export function PureViewServer({flow, message, content}: PureViewServerProps) { + const [data, setData] = useState({ + lines: [], + description: "", + }) + + const dispatch = useAppDispatch(), + showFullContent: boolean = useAppSelector(state => state.ui.flow.showFullContent), + maxLines: number = useAppSelector(state => state.ui.flow.maxContentLines) + + let lines = showFullContent ? data.lines : data.lines?.slice(0, maxLines) + + useEffect(() => { + setContentView({flow, message, content}) + }, [flow, message, content]) + + const setContentView = (props) => { + try { + setData(JSON.parse(props.content)) + }catch(err) { + setData({lines: [], description: err.message}) + } + + dispatch(setContentViewDescription(props.contentView !== data.description ? data.description : '')) + dispatch(setContent(data.lines)) + } + + return ( +
+ {ViewImage.matches(message) && } +
+                {lines.map((line, i) =>
+                    
+ {line.map((element, j) => { + let [style, text] = element + return ( + + {text} + + ) + })} +
+ )} +
+
+ ) + +} + +const ViewServer = withContentLoader(PureViewServer) + +export { Edit, ViewServer, ViewImage } diff --git a/web/src/js/components/ContentView/DownloadContentButton.jsx b/web/src/js/components/ContentView/DownloadContentButton.tsx similarity index 59% rename from web/src/js/components/ContentView/DownloadContentButton.jsx rename to web/src/js/components/ContentView/DownloadContentButton.tsx index f32a19ca4..b538038d5 100644 --- a/web/src/js/components/ContentView/DownloadContentButton.jsx +++ b/web/src/js/components/ContentView/DownloadContentButton.tsx @@ -1,13 +1,12 @@ import React from 'react' import { MessageUtils } from "../../flow/utils" -import PropTypes from 'prop-types' -DownloadContentButton.propTypes = { - flow: PropTypes.object.isRequired, - message: PropTypes.object.isRequired, +type DownloadContentButtonProps = { + flow: object, + message: object, } -export default function DownloadContentButton({ flow, message }) { +export default function DownloadContentButton({ flow, message }: DownloadContentButtonProps) { return ( void, + uploadContent: () => any, +} + + +export function ContentEmpty({ flow, message }: ContentProps) { return (
No {flow.request === message ? 'request' : 'response'} content. @@ -11,7 +22,7 @@ export function ContentEmpty({ flow, message }) { ) } -export function ContentMissing({ flow, message }) { +export function ContentMissing({ flow, message }: ContentProps) { return (
{flow.request === message ? 'Request' : 'Response'} content missing. @@ -19,7 +30,7 @@ export function ContentMissing({ flow, message }) { ) } -export function ContentTooLarge({ message, onClick, uploadContent, flow }) { +export function ContentTooLarge({ message, onClick, uploadContent, flow }: ContentTooLargeProps) { return (
diff --git a/web/src/js/components/ContentView/ShowFullContentButton.jsx b/web/src/js/components/ContentView/ShowFullContentButton.jsx deleted file mode 100644 index c6d8c2f20..000000000 --- a/web/src/js/components/ContentView/ShowFullContentButton.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { render } from 'react-dom'; -import Button from '../common/Button'; -import { setShowFullContent } from '../../ducks/ui/flow' - - - -ShowFullContentButton.propTypes = { - setShowFullContent: PropTypes.func.isRequired, - showFullContent: PropTypes.bool.isRequired -} - -export function ShowFullContentButton ( {setShowFullContent, showFullContent, visibleLines, contentLines} ){ - - return ( - !showFullContent && -
- - {visibleLines}/{contentLines} are visible   -
- ) -} - -export default connect( - state => ({ - showFullContent: state.ui.flow.showFullContent, - visibleLines: state.ui.flow.maxContentLines, - contentLines: state.ui.flow.content.length - - }), - { - setShowFullContent - } -)(ShowFullContentButton) - diff --git a/web/src/js/components/ContentView/ShowFullContentButton.tsx b/web/src/js/components/ContentView/ShowFullContentButton.tsx new file mode 100644 index 000000000..9c1b15b88 --- /dev/null +++ b/web/src/js/components/ContentView/ShowFullContentButton.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import Button from '../common/Button'; +import { setShowFullContent } from '../../ducks/ui/flow' +import {useAppDispatch, useAppSelector} from "../../ducks"; + +export default function ShowFullContentButton() { + const dispatch = useAppDispatch(), + showFullContent = useAppSelector(state => state.ui.flow.showFullContent), + visibleLines = useAppSelector(state => state.ui.flow.maxContentLines), + contentLines = useAppSelector(state => state.ui.flow.content.length) + + return ( + !showFullContent && +
+ + {visibleLines}/{contentLines} are visible   +
+ ) +} + diff --git a/web/src/js/components/ContentView/UploadContentButton.jsx b/web/src/js/components/ContentView/UploadContentButton.tsx similarity index 62% rename from web/src/js/components/ContentView/UploadContentButton.jsx rename to web/src/js/components/ContentView/UploadContentButton.tsx index 847d4eb06..9b7281409 100644 --- a/web/src/js/components/ContentView/UploadContentButton.jsx +++ b/web/src/js/components/ContentView/UploadContentButton.tsx @@ -1,12 +1,11 @@ import React from 'react' -import PropTypes from 'prop-types' import FileChooser from '../common/FileChooser' -UploadContentButton.propTypes = { - uploadContent: PropTypes.func.isRequired, +type UploadContentButtonProps = { + uploadContent: () => any, } -export default function UploadContentButton({ uploadContent }) { +export default function UploadContentButton({ uploadContent }: UploadContentButtonProps) { return ( void, + children?: React.ReactNode, + icon?: string, + disabled?: boolean, + className?: string, + title?: string, } -export default function Button({ onClick, children, icon, disabled, className, title }) { +export default function Button({ onClick, children, icon, disabled, className, title }: ButtonProps) { return (