diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
index ec66a44e0..fdc3e93c9 100644
--- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
+++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
@@ -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 ;
}
+ case 'script': {
+ return ;
+ }
+ case 'tests': {
+ return ;
+ }
default: {
return
404 | Not found
;
}
@@ -67,6 +75,12 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
selectTab('headers')}>
Headers
+ selectTab('script')}>
+ Script
+
+ selectTab('tests')}>
+ Tests
+
{/* Moved to post mvp */}
{/* selectTab('auth')}>Auth
*/}
{focusedTab.requestPaneTab === 'body' ? (
diff --git a/packages/bruno-app/src/components/RequestPane/Script/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Script/StyledWrapper.js
new file mode 100644
index 000000000..9f7583222
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Script/StyledWrapper.js
@@ -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;
diff --git a/packages/bruno-app/src/components/RequestPane/Script/index.js b/packages/bruno-app/src/components/RequestPane/Script/index.js
new file mode 100644
index 000000000..7887428bc
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Script/index.js
@@ -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 (
+
+
+
+ );
+};
+
+export default Script;
diff --git a/packages/bruno-app/src/components/RequestPane/Tests/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Tests/StyledWrapper.js
new file mode 100644
index 000000000..9f7583222
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Tests/StyledWrapper.js
@@ -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;
diff --git a/packages/bruno-app/src/components/RequestPane/Tests/index.js b/packages/bruno-app/src/components/RequestPane/Tests/index.js
new file mode 100644
index 000000000..43a1b9162
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Tests/index.js
@@ -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 (
+
+
+
+ );
+};
+
+export default Tests;
diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js
index 56398afc4..4328235d7 100644
--- a/packages/bruno-app/src/components/SingleLineEditor/index.js
+++ b/packages/bruno-app/src/components/SingleLineEditor/index.js
@@ -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();
}
},
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index 1809ba68f..382ba0288 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -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,
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index 1bf2894ad..45cb01df8 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -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
}
};
diff --git a/packages/bruno-electron/src/ipc/local-collection.js b/packages/bruno-electron/src/ipc/collection.js
similarity index 100%
rename from packages/bruno-electron/src/ipc/local-collection.js
rename to packages/bruno-electron/src/ipc/collection.js
diff --git a/packages/bruno-lang/src/index.js b/packages/bruno-lang/src/index.js
index 1dea17e47..1aa0ecd0c 100644
--- a/packages/bruno-lang/src/index.js
+++ b/packages/bruno-lang/src/index.js
@@ -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;
};
diff --git a/packages/bruno-lang/src/script-tag.js b/packages/bruno-lang/src/script-tag.js
new file mode 100644
index 000000000..8bc7e6de9
--- /dev/null
+++ b/packages/bruno-lang/src/script-tag.js
@@ -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;
diff --git a/packages/bruno-lang/src/tests-tag.js b/packages/bruno-lang/src/tests-tag.js
new file mode 100644
index 000000000..a2c0d573f
--- /dev/null
+++ b/packages/bruno-lang/src/tests-tag.js
@@ -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;
diff --git a/packages/bruno-lang/tests/bru-to-json.spec.js b/packages/bruno-lang/tests/bru-to-json.spec.js
index 98919b6c5..3fb2e685d 100644
--- a/packages/bruno-lang/tests/bru-to-json.spec.js
+++ b/packages/bruno-lang/tests/bru-to-json.spec.js
@@ -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: ""
}
});
});
diff --git a/packages/bruno-lang/tests/fixtures/request.bru b/packages/bruno-lang/tests/fixtures/request.bru
index 660bcdbaa..ee6d89ef6 100644
--- a/packages/bruno-lang/tests/fixtures/request.bru
+++ b/packages/bruno-lang/tests/fixtures/request.bru
@@ -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
diff --git a/packages/bruno-lang/tests/json-to-bru.spec.js b/packages/bruno-lang/tests/json-to-bru.spec.js
index 152091938..2f48ce8cb 100644
--- a/packages/bruno-lang/tests/json-to-bru.spec.js
+++ b/packages/bruno-lang/tests/json-to-bru.spec.js
@@ -80,7 +80,9 @@ describe('bruToJson', () => {
"value": "governingdynamics"
}
]
- }
+ },
+ "script": "const foo='bar';",
+ "tests": "bruno.test('200 ok', () => {});"
}
};
diff --git a/packages/bruno-lang/tests/script-tag.spec.js b/packages/bruno-lang/tests/script-tag.spec.js
new file mode 100644
index 000000000..0d9e0df85
--- /dev/null
+++ b/packages/bruno-lang/tests/script-tag.spec.js
@@ -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);
+ });
+});
diff --git a/packages/bruno-lang/tests/tests-tag.spec.js b/packages/bruno-lang/tests/tests-tag.spec.js
new file mode 100644
index 000000000..c7aba48ea
--- /dev/null
+++ b/packages/bruno-lang/tests/tests-tag.spec.js
@@ -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);
+ });
+});
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index 5757a9db8..a7fbb3015 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -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({