feat: script and tests functionality

This commit is contained in:
Anoop M D 2023-01-22 23:39:16 +05:30
parent b70bbf78b1
commit 5c8d0a9e8a
18 changed files with 360 additions and 12 deletions

View File

@ -7,6 +7,8 @@ import QueryParams from 'components/RequestPane/QueryParams';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
import Script from 'components/RequestPane/Script';
import Tests from 'components/RequestPane/Tests';
import StyledWrapper from './StyledWrapper';
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
@ -34,6 +36,12 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
case 'script': {
return <Script item={item} collection={collection} />;
}
case 'tests': {
return <Tests item={item} collection={collection} />;
}
default: {
return <div className="mt-4">404 | Not found</div>;
}
@ -67,6 +75,12 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
</div>
{/* Moved to post mvp */}
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
{focusedTab.requestPaneTab === 'body' ? (

View File

@ -0,0 +1,10 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
/* todo: find a better way */
height: calc(100vh - 220px);
}
`;
export default StyledWrapper;

View File

@ -0,0 +1,39 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestScript } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const Script = ({ item, collection }) => {
const dispatch = useDispatch();
const script = item.draft ? get(item, 'draft.request.script') : get(item, 'request.script');
const onEdit = (value) => {
dispatch(
updateRequestScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full">
<CodeEditor
collection={collection} value={script || ''}
onEdit={onEdit}
mode='javascript'
onRun={onRun}
onSave={onSave}
/>
</StyledWrapper>
);
};
export default Script;

View File

@ -0,0 +1,10 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
/* todo: find a better way */
height: calc(100vh - 220px);
}
`;
export default StyledWrapper;

View File

@ -0,0 +1,39 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestTests } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const Tests = ({ item, collection }) => {
const dispatch = useDispatch();
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
const onEdit = (value) => {
dispatch(
updateRequestTests({
tests: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full">
<CodeEditor
collection={collection} value={tests || ''}
onEdit={onEdit}
mode='javascript'
onRun={onRun}
onSave={onSave}
/>
</StyledWrapper>
);
};
export default Tests;

View File

@ -53,9 +53,7 @@ class SingleLineEditor extends Component {
}
},
'Cmd-S': () => {
console.log('cmd-s');
if (this.props.onSave) {
console.log('cmd-s +');
this.props.onSave();
}
},

View File

@ -589,6 +589,34 @@ export const collectionsSlice = createSlice({
}
}
},
updateRequestScript: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
item.draft.request.script = action.payload.script;
}
}
},
updateRequestTests: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
item.draft.request.tests = action.payload.tests;
}
}
},
updateRequestMethod: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@ -790,6 +818,8 @@ export const {
updateRequestBodyMode,
updateRequestBody,
updateRequestGraphqlQuery,
updateRequestScript,
updateRequestTests,
updateRequestMethod,
collectionAddFileEvent,
collectionAddDirectoryEvent,

View File

@ -280,7 +280,9 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
graphql: si.draft.request.body.graphql,
formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded),
multipartForm: copyMultipartFormParams(si.draft.request.body.multipartForm)
}
},
script: si.draft.request.script,
tests: si.draft.request.tests
};
}
} else {
@ -298,7 +300,9 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
graphql: si.request.body.graphql,
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded),
multipartForm: copyMultipartFormParams(si.request.body.multipartForm)
}
},
script: si.request.script,
tests: si.request.tests
};
}
}
@ -343,7 +347,9 @@ export const transformRequestToSaveToFilesystem = (item) => {
url: _item.request.url,
params: [],
headers: [],
body: _item.request.body
body: _item.request.body,
script: _item.request.script,
tests: _item.request.tests
}
};

View File

@ -21,6 +21,8 @@ const {
bodyFormUrlEncodedTag,
bodyMultipartFormTag
} = require('./body-tag');
const scriptTag = require('./script-tag');
const testsTag = require('./tests-tag');
const bruToJson = (fileContents) => {
const parser = many(choice([
@ -33,6 +35,8 @@ const bruToJson = (fileContents) => {
bodyXmlTag,
bodyFormUrlEncodedTag,
bodyMultipartFormTag,
scriptTag,
testsTag,
anyChar
]));
@ -50,7 +54,9 @@ const bruToJson = (fileContents) => {
url: parsed.url || '',
params: parsed.params || [],
headers: parsed.headers || [],
body: parsed.body || {mode: 'none'}
body: parsed.body || {mode: 'none'},
script: parsed.script ? outdentString(parsed.script) : '',
tests: parsed.tests ? outdentString(parsed.tests) : ''
}
};
@ -85,7 +91,9 @@ const jsonToBru = (json) => {
url,
params,
headers,
body
body,
script,
tests
}
} = json;
@ -161,6 +169,22 @@ ${body.multipartForm.map(item => ` ${item.enabled ? 1 : 0} ${item.name} ${item.
`;
}
if(script && script.length) {
bru += `
script
${indentString(script)}
/script
`;
}
if(tests && tests.length) {
bru += `
tests
${indentString(tests)}
/tests
`;
}
return bru;
};

View File

@ -0,0 +1,16 @@
const {
between,
regex,
everyCharUntil
} = require("arcsecond");
const scriptBegin = regex(/^script\s*\r?\n/);
const scriptEnd = regex(/^[\r?\n]+\/script[\s\r?\n]*/);
const scriptTag = between(scriptBegin)(scriptEnd)(everyCharUntil(scriptEnd)).map((script) => {
return {
script: script
};
});
module.exports = scriptTag;

View File

@ -0,0 +1,16 @@
const {
between,
regex,
everyCharUntil
} = require("arcsecond");
const testsBegin = regex(/^tests\s*\r?\n/);
const testsEnd = regex(/^[\r?\n]+\/tests[\s\r?\n]*/);
const testsTag = between(testsBegin)(testsEnd)(everyCharUntil(testsEnd)).map((tests) => {
return {
tests: tests
};
});
module.exports = testsTag;

View File

@ -83,7 +83,9 @@ describe('bruToJson', () => {
"value": "governingdynamics"
}
]
}
},
"script": "const foo='bar';",
"tests": "bruno.test('200 ok', () => {});"
}
});
});
@ -110,7 +112,9 @@ seq 1
url: '',
params: [],
headers: [],
body: { mode: 'none' }
body: { mode: 'none' },
script: "",
tests: ""
}
});
});

View File

@ -49,3 +49,11 @@ body(type=multipart-form)
1 username nash
0 password governingdynamics
/body
script
const foo='bar';
/script
tests
bruno.test('200 ok', () => {});
/tests

View File

@ -80,7 +80,9 @@ describe('bruToJson', () => {
"value": "governingdynamics"
}
]
}
},
"script": "const foo='bar';",
"tests": "bruno.test('200 ok', () => {});"
}
};

View File

@ -0,0 +1,65 @@
const scriptTag = require('../src/script-tag');
describe('scriptTag', () => {
// simple case
it('should parse script contents - 1', () => {
const input = 'script\n const foo = "bar";\n/script';
const result = scriptTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.script).toEqual(' const foo = "bar";');
});
// simple case with extra spaces
it('should parse script contents - 2', () => {
const input = 'script \n const foo = "bar";\n/script';
const result = scriptTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.script).toEqual(' const foo = "bar";');
});
// simple case with extra spaces
it('should parse script contents - 3', () => {
const input = 'script \n const foo = "bar";\n/script ';
const result = scriptTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.script).toEqual(' const foo = "bar";');
});
// simple case with extra spaces
it('should parse script contents - 4', () => {
const input = 'script \n const foo = "bar";\n/script \n';
const result = scriptTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.script).toEqual(' const foo = "bar";');
});
// simple case with extra spaces
it('should parse script contents - 5', () => {
const input = 'script \n const foo = "bar";\n/script \n ';
const result = scriptTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.script).toEqual(' const foo = "bar";');
});
// simple case with extra spaces
it('should parse script contents - 6', () => {
const input = 'script \n const foo = "bar";\n/script \n \n';
const result = scriptTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.script).toEqual(' const foo = "bar";');
});
// error case - missing script start tag
it('should fail to parse when script start tag is missing', () => {
const input = ' const foo = "bar";\n/script';
const result = scriptTag.run(input);
expect(result.isError).toBe(true);
});
// error case - missing script end tag
it('should fail to parse when script end tag is missing', () => {
const input = 'script\n const foo = "bar";';
const result = scriptTag.run(input);
expect(result.isError).toBe(true);
});
});

View File

@ -0,0 +1,65 @@
const testsTag = require('../src/tests-tag');
describe('testsTag', () => {
// simple case
it('should parse tests contents - 1', () => {
const input = 'tests\n bruno.test("200 ok", () => {});\n/tests';
const result = testsTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.tests).toEqual(' bruno.test("200 ok", () => {});');
});
// simple case with extra spaces
it('should parse tests contents - 2', () => {
const input = 'tests \n bruno.test("200 ok", () => {});\n/tests';
const result = testsTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.tests).toEqual(' bruno.test("200 ok", () => {});');
});
// simple case with extra spaces
it('should parse tests contents - 3', () => {
const input = 'tests \n bruno.test("200 ok", () => {});\n/tests ';
const result = testsTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.tests).toEqual(' bruno.test("200 ok", () => {});');
});
// simple case with extra spaces
it('should parse tests contents - 4', () => {
const input = 'tests \n bruno.test("200 ok", () => {});\n/tests \n';
const result = testsTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.tests).toEqual(' bruno.test("200 ok", () => {});');
});
// simple case with extra spaces
it('should parse tests contents - 5', () => {
const input = 'tests \n bruno.test("200 ok", () => {});\n/tests \n ';
const result = testsTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.tests).toEqual(' bruno.test("200 ok", () => {});');
});
// simple case with extra spaces
it('should parse tests contents - 6', () => {
const input = 'tests \n bruno.test("200 ok", () => {});\n/tests \n \n';
const result = testsTag.run(input);
expect(result.isError).toBe(false);
expect(result.result.tests).toEqual(' bruno.test("200 ok", () => {});');
});
// error case - missing tests start tag
it('should fail to parse when tests start tag is missing', () => {
const input = ' bruno.test("200 ok", () => {});\n/tests';
const result = testsTag.run(input);
expect(result.isError).toBe(true);
});
// error case - missing tests end tag
it('should fail to parse when tests end tag is missing', () => {
const input = 'tests\n bruno.test("200 ok", () => {});';
const result = testsTag.run(input);
expect(result.isError).toBe(true);
});
});

View File

@ -41,7 +41,7 @@ const requestBodySchema = Yup.object({
xml: Yup.string().max(10240, 'xml must be 10240 characters or less').nullable(),
formUrlEncoded: Yup.array().of(keyValueSchema).nullable(),
multipartForm: Yup.array().of(keyValueSchema).nullable(),
graphql: graphqlBodySchema.nullable(),
graphql: graphqlBodySchema.nullable()
}).noUnknown(true).strict();
// Right now, the request schema is very tightly coupled with http request
@ -52,7 +52,9 @@ const requestSchema = Yup.object({
method: requestMethodSchema,
headers: Yup.array().of(keyValueSchema).required('headers are required'),
params: Yup.array().of(keyValueSchema).required('params are required'),
body: requestBodySchema
body: requestBodySchema,
script: Yup.string().nullable(),
tests: Yup.string().nullable()
}).noUnknown(true).strict();
const itemSchema = Yup.object({