mirror of
https://github.com/usebruno/bruno.git
synced 2024-12-22 14:41:04 +01:00
feat: script and tests functionality
This commit is contained in:
parent
b70bbf78b1
commit
5c8d0a9e8a
@ -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' ? (
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
39
packages/bruno-app/src/components/RequestPane/Tests/index.js
Normal file
39
packages/bruno-app/src/components/RequestPane/Tests/index.js
Normal 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;
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
16
packages/bruno-lang/src/script-tag.js
Normal file
16
packages/bruno-lang/src/script-tag.js
Normal 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;
|
16
packages/bruno-lang/src/tests-tag.js
Normal file
16
packages/bruno-lang/src/tests-tag.js
Normal 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;
|
@ -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: ""
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -80,7 +80,9 @@ describe('bruToJson', () => {
|
||||
"value": "governingdynamics"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"script": "const foo='bar';",
|
||||
"tests": "bruno.test('200 ok', () => {});"
|
||||
}
|
||||
};
|
||||
|
||||
|
65
packages/bruno-lang/tests/script-tag.spec.js
Normal file
65
packages/bruno-lang/tests/script-tag.spec.js
Normal 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);
|
||||
});
|
||||
});
|
65
packages/bruno-lang/tests/tests-tag.spec.js
Normal file
65
packages/bruno-lang/tests/tests-tag.spec.js
Normal 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);
|
||||
});
|
||||
});
|
@ -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({
|
||||
|
Loading…
Reference in New Issue
Block a user