diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml
index 7783d5c..687fc5a 100644
--- a/.github/workflows/npmpublish.yml
+++ b/.github/workflows/npmpublish.yml
@@ -6,7 +6,7 @@ name: SASjs Build and Publish
on:
push:
branches:
- - main
+ - master
jobs:
build:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 398693c..135d8de 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -16,7 +16,7 @@ Tests are run using cypress. Before running tests, you need to define the follow
```
-filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas?_=1";
+filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
filename ft15f001 temp;
parmcards4;
@@ -40,18 +40,13 @@ parmcards4;
# Viya
```
-filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas";
+filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
-
filename ft15f001 temp;
parmcards4;
+ %webout(FETCH)
%webout(OPEN)
- %global sasjs_tables;
- %let sasjs_tables=&sasjs_tables;
- %put &=sasjs_tables;
- %let sasjs_tables=&sasjs_tables;
%macro x();
- %global sasjs_tables;
%do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i);
%webout(OBJ,&table)
@@ -60,13 +55,11 @@ parmcards4;
%x()
%webout(CLOSE)
;;;;
-%mv_createwebservice(path=/Public/app/common,name=sendObj)
+%mp_createwebservice(path=/Public/app/common,name=sendObj)
filename ft15f001 temp;
parmcards4;
+ %webout(FETCH)
%webout(OPEN)
- %global sasjs_tables;
- %let sasjs_tables=&sasjs_tables;
- %put &=sasjs_tables;
%macro x();
%do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i);
@@ -76,7 +69,7 @@ parmcards4;
%x()
%webout(CLOSE)
;;;;
-%mv_createwebservice(path=/Public/app/common,name=sendArr)
+%mp_createwebservice(path=/Public/app/common,name=sendArr)
```
The above services will return anything you send. To run the tests simply launch `npm run cypress`.
diff --git a/README.md b/README.md
index 839664c..47ef0f1 100644
--- a/README.md
+++ b/README.md
@@ -10,13 +10,13 @@ SASjs is a open-source framework for building Web Apps on SASĀ® platforms. You c
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
-If you are short on time and just need to build an app quickly, then check out [this video](https://vimeo.com/393161794) and the [react-seed-app](https://github.com/macropeople/react-seed-app) which provides some boilerplate.
+If you are short on time and just need to build an app quickly, then check out [this video](https://vimeo.com/393161794) and the [react-seed-app](https://github.com/sasjs/react-seed-app) which provides some boilerplate.
For more information on building web apps with SAS, check out [sasjs.io](https://sasjs.io)
## None of this makes sense. How do I build an app with it?
-Ok ok. Deploy this [example.html](https://github.com/sasjs/adapter/blob/main/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
+Ok ok. Deploy this [example.html](https://raw.githubusercontent.com/sasjs/adapter/master/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
The backend part can be deployed as follows:
@@ -43,6 +43,6 @@ You now have a simple web app with a backend service!
# More resources
-For more information specific to this adapter you can check out this [user guide](https://sasjs.io/sasjs/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
+For more information specific to this adapter you can check out this [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.
diff --git a/docs/index.html b/docs/index.html
index bf0b429..a3514df 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -199,7 +199,7 @@ parmcards4;
For more information specific to this adapter you can check out
this
- user guide or
+ user guide or
the
technical documentation.
@@ -225,7 +225,7 @@ parmcards4;
diff --git a/package-lock.json b/package-lock.json
index c11a65b..b4bbfbe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1648,9 +1648,9 @@
}
},
"@types/jest": {
- "version": "26.0.4",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.4.tgz",
- "integrity": "sha512-4fQNItvelbNA9+sFgU+fhJo8ZFF+AS4Egk3GWwCW2jFtViukXbnztccafAdLhzE/0EiCogljtQQXP8aQ9J7sFg==",
+ "version": "26.0.5",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.5.tgz",
+ "integrity": "sha512-heU+7w8snfwfjtcj2H458aTx3m5unIToOJhx75ebHilBiiQ39OIdA18WkG4LP08YKeAoWAGvWg8s+22w/PeJ6w==",
"dev": true,
"requires": {
"jest-diff": "^25.2.1",
@@ -14874,9 +14874,9 @@
}
},
"ts-loader": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.5.tgz",
- "integrity": "sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.1.tgz",
+ "integrity": "sha512-I9Nmly0ufJoZRMuAT9d5ijsC2B7oSPvUnOJt/GhgoATlPGYfa17VicDKPcqwUCrHpOkCxr/ybLYwbnS4cOxmvQ==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
@@ -15073,9 +15073,9 @@
}
},
"typescript": {
- "version": "3.9.6",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz",
- "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==",
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
+ "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true
},
"uglify-js": {
diff --git a/package.json b/package.json
index b16936e..2cdaef2 100644
--- a/package.json
+++ b/package.json
@@ -22,9 +22,6 @@
{
"pkgRoot": "/build"
}
- ],
- "branches": [
- "main"
]
},
"keywords": [
@@ -40,7 +37,7 @@
"license": "ISC",
"devDependencies": {
"@types/isomorphic-fetch": "0.0.35",
- "@types/jest": "^26.0.4",
+ "@types/jest": "^26.0.5",
"cp": "^0.2.0",
"jest": "^25.5.4",
"path": "^0.12.7",
@@ -48,13 +45,13 @@
"rimraf": "^3.0.2",
"semantic-release": "^17.1.1",
"ts-jest": "^25.5.1",
- "ts-loader": "^7.0.5",
+ "ts-loader": "^8.0.1",
"tslint": "^6.1.2",
"tslint-config-prettier": "^1.18.0",
"typedoc": "^0.17.8",
"typedoc-neo-theme": "^1.0.9",
"typedoc-plugin-external-module-name": "^4.0.3",
- "typescript": "^3.9.6",
+ "typescript": "^3.9.7",
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
diff --git a/sasjs-tests/package-lock.json b/sasjs-tests/package-lock.json
index 3e75506..a5c2031 100644
--- a/sasjs-tests/package-lock.json
+++ b/sasjs-tests/package-lock.json
@@ -1378,11 +1378,51 @@
}
}
},
+ "@sasjs/test-framework": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@sasjs/test-framework/-/test-framework-1.3.0.tgz",
+ "integrity": "sha512-vrbRFUhNUShLlNFZO+XwVwFLXDLApQG9zOPx00xhQ8IUA0cSDFFmf2mP/KBdFCxa1REaR6GHvMctUj+xRZo9QQ==",
+ "requires": {
+ "@types/react-highlight.js": "^1.0.0",
+ "moment": "^2.27.0",
+ "react-highlight.js": "^1.0.7",
+ "semantic-ui-css": "^2.4.1",
+ "semantic-ui-react": "^1.0.0"
+ }
+ },
+ "@semantic-ui-react/event-stack": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.1.tgz",
+ "integrity": "sha512-SA7VOu/tY3OkooR++mm9voeQrJpYXjJaMHO1aFCcSouS2xhqMR9Gnz0LEGLOR0h9ueWPBKaQzKIrx3FTTJZmUQ==",
+ "requires": {
+ "exenv": "^1.2.2",
+ "prop-types": "^15.6.2"
+ }
+ },
"@sheerun/mutationobserver-shim": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz",
"integrity": "sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw=="
},
+ "@stardust-ui/react-component-event-listener": {
+ "version": "0.38.0",
+ "resolved": "https://registry.npmjs.org/@stardust-ui/react-component-event-listener/-/react-component-event-listener-0.38.0.tgz",
+ "integrity": "sha512-sIP/e0dyOrrlb8K7KWumfMxj/gAifswTBC4o68Aa+C/GA73ccRp/6W1VlHvF/dlOR4KLsA+5SKnhjH36xzPsWg==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@stardust-ui/react-component-ref": {
+ "version": "0.38.0",
+ "resolved": "https://registry.npmjs.org/@stardust-ui/react-component-ref/-/react-component-ref-0.38.0.tgz",
+ "integrity": "sha512-xjs6WnvJVueSIXMWw0C3oWIgAPpcD03qw43oGOjUXqFktvpNkB73JoKIhS4sCrtQxBdct75qqr4ZL6JiyPcESw==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.6.3"
+ }
+ },
"@svgr/babel-plugin-add-jsx-attribute": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
@@ -1858,6 +1898,14 @@
"@types/react": "*"
}
},
+ "@types/react-highlight.js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@types/react-highlight.js/-/react-highlight.js-1.0.0.tgz",
+ "integrity": "sha512-5VXEuo2O9L66y/2GDQSGFTggQkpOvDc/p2ma1KHadu7o/H720HK3Fr83epd4wtQky7B/RoCPat0SKyhlhiUo7A==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-router": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
@@ -3699,6 +3747,11 @@
}
}
},
+ "classnames": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ },
"clean-css": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
@@ -4117,6 +4170,15 @@
"sha.js": "^2.4.8"
}
},
+ "create-react-context": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz",
+ "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==",
+ "requires": {
+ "gud": "^1.0.0",
+ "warning": "^4.0.3"
+ }
+ },
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -5641,6 +5703,11 @@
"strip-eof": "^1.0.0"
}
},
+ "exenv": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
+ "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
+ },
"exit": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@@ -6510,6 +6577,11 @@
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
},
+ "gud": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
+ "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
+ },
"gzip-size": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
@@ -6654,6 +6726,11 @@
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
},
+ "highlight.js": {
+ "version": "9.18.3",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.3.tgz",
+ "integrity": "sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ=="
+ },
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
@@ -8006,6 +8083,11 @@
}
}
},
+ "jquery": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
+ "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
+ },
"js-base64": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.2.tgz",
@@ -8155,6 +8237,11 @@
"object.assign": "^4.1.0"
}
},
+ "keyboard-key": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz",
+ "integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ=="
+ },
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -8919,6 +9006,11 @@
"minimist": "^1.2.5"
}
},
+ "moment": {
+ "version": "2.27.0",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
+ "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
+ },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -9912,6 +10004,11 @@
"ts-pnp": "^1.1.6"
}
},
+ "popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
+ },
"portfinder": {
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
@@ -11353,11 +11450,34 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
},
+ "react-highlight.js": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/react-highlight.js/-/react-highlight.js-1.0.7.tgz",
+ "integrity": "sha512-OVPKnV0ZvU+V//HExwbV8M9CWy49Eo/9y9pBN2OsNWUFPN6dE4YZBLmJW/5sM2DxI5v/QQLyxOnTnSSfGCP+9Q==",
+ "requires": {
+ "highlight.js": "^9.3.0",
+ "prop-types": "^15.6.0"
+ }
+ },
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-popper": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
+ "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "create-react-context": "^0.3.0",
+ "deep-equal": "^1.1.1",
+ "popper.js": "^1.14.4",
+ "prop-types": "^15.6.1",
+ "typed-styles": "^0.0.7",
+ "warning": "^4.0.2"
+ }
+ },
"react-router": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
@@ -12097,6 +12217,32 @@
"node-forge": "0.9.0"
}
},
+ "semantic-ui-css": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/semantic-ui-css/-/semantic-ui-css-2.4.1.tgz",
+ "integrity": "sha512-Pkp0p9oWOxlH0kODx7qFpIRYpK1T4WJOO4lNnpNPOoWKCrYsfHqYSKgk5fHfQtnWnsAKy7nLJMW02bgDWWFZFg==",
+ "requires": {
+ "jquery": "x.*"
+ }
+ },
+ "semantic-ui-react": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-1.0.0.tgz",
+ "integrity": "sha512-85mYHYuDBNa6la1BgKwuOSD1vcIPsFQEXRxGsZ9pUtE4iHlEcylF+x46NYHIGbBjlys63SpNH3PtK6VyZj9LBw==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "@semantic-ui-react/event-stack": "^3.1.0",
+ "@stardust-ui/react-component-event-listener": "~0.38.0",
+ "@stardust-ui/react-component-ref": "~0.38.0",
+ "classnames": "^2.2.6",
+ "keyboard-key": "^1.0.4",
+ "lodash": "^4.17.15",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.6",
+ "react-popper": "^1.3.4",
+ "shallowequal": "^1.1.0"
+ }
+ },
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -12286,6 +12432,11 @@
}
}
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -13454,6 +13605,11 @@
"mime-types": "~2.1.24"
}
},
+ "typed-styles": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
+ "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
+ },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -13755,6 +13911,14 @@
"makeerror": "1.0.x"
}
},
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"watchpack": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",
diff --git a/sasjs-tests/package.json b/sasjs-tests/package.json
index 29b9734..278075a 100644
--- a/sasjs-tests/package.json
+++ b/sasjs-tests/package.json
@@ -5,6 +5,7 @@
"private": true,
"dependencies": {
"@sasjs/adapter": "^1.0.5",
+ "@sasjs/test-framework": "^1.3.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
diff --git a/sasjs-tests/src/App.scss b/sasjs-tests/src/App.scss
deleted file mode 100644
index a61d835..0000000
--- a/sasjs-tests/src/App.scss
+++ /dev/null
@@ -1,102 +0,0 @@
-.app {
- padding: 16px;
-
- .controls {
- display: flex;
- align-items: center;
- .debug-toggle,
- .app-loc-input,
- .submit-button {
- margin: 16px 0;
- }
-
- .row {
- margin: 16px;
-
- &.app-loc {
- width: 20vw;
- }
- }
-
- .submit-button {
- padding: 16px;
- font-size: 1.25em;
- }
-
- .app-loc-input {
- width: 100%;
- }
- }
-
- .debug-toggle {
- display: inline-flex;
- justify-content: center;
- align-items: center;
-
- .label {
- padding: 0 8px;
- font-size: 1.25em;
- }
- }
-
- $height: 40px;
- $width: 70px;
- .switch {
- position: relative;
- display: inline-flex;
- width: $width;
- height: $height;
-
- input[type="checkbox"] {
- display: none;
- }
- input:checked + .knob {
- animation: colorChange 0.4s linear forwards;
- }
- input:checked + .knob:before {
- animation: turnON 0.4s linear forwards;
- }
- }
-
- @keyframes colorChange {
- from {
- background-color: #ccc;
- }
- 50% {
- background-color: #a4d9ad;
- }
- to {
- background-color: #4bd663;
- }
- }
- @keyframes turnON {
- from {
- transform: translateX(0px);
- }
- to {
- transform: translateX($width - ($height * 0.99));
- box-shadow: -10px 0px 44px 0px #434343;
- }
- }
-
- .knob {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #ccc;
- border-radius: $height;
- }
-
- .knob:before {
- position: absolute;
- background-color: white;
- content: "";
- left: $height * 0.1;
- top: $height * 0.1;
- width: ($height * 0.8);
- height: ($height * 0.8);
- border-radius: 50%;
- }
-}
diff --git a/sasjs-tests/src/App.tsx b/sasjs-tests/src/App.tsx
index 4ede90b..a7ee15e 100644
--- a/sasjs-tests/src/App.tsx
+++ b/sasjs-tests/src/App.tsx
@@ -1,56 +1,30 @@
import React, { ReactElement, useState, useContext, useEffect } from "react";
-import "./App.scss";
-import TestSuiteRunner from "./TestSuiteRunner";
-import { AppContext } from "./context/AppContext";
+import { TestSuiteRunner, TestSuite, AppContext } from "@sasjs/test-framework";
+import { basicTests } from "./testSuites/Basic";
+import { sendArrTests, sendObjTests } from "./testSuites/RequestData";
+import { specialCaseTests } from "./testSuites/SpecialCases";
+import { sasjsRequestTests } from "./testSuites/SasjsRequests";
+import "@sasjs/test-framework/dist/index.css";
const App = (): ReactElement<{}> => {
- const [appLoc, setAppLoc] = useState("");
- const [debug, setDebug] = useState(false);
- const { adapter } = useContext(AppContext);
+ const { adapter, config } = useContext(AppContext);
+ const [testSuites, setTestSuites] = useState
([]);
useEffect(() => {
if (adapter) {
- adapter.setDebugState(debug);
+ setTestSuites([
+ basicTests(adapter, config.userName, config.password),
+ sendArrTests(adapter),
+ sendObjTests(adapter),
+ specialCaseTests(adapter),
+ sasjsRequestTests(adapter),
+ ]);
}
- }, [debug, adapter]);
-
- useEffect(() => {
- if (appLoc && adapter) {
- adapter.setSASjsConfig({ ...adapter.getSasjsConfig(), appLoc });
- }
- }, [appLoc, adapter]);
-
- useEffect(() => {
- setAppLoc(adapter.getSasjsConfig().appLoc);
- }, [adapter]);
+ }, [adapter, config]);
return (
-
-
-
Debug
-
-
- setDebug(e.target.checked)}
- />
-
-
-
-
-
- App Loc
- setAppLoc(e.target.value)}
- placeholder="AppLoc"
- />
-
-
- {adapter &&
}
+ {adapter && testSuites &&
}
);
};
diff --git a/sasjs-tests/src/Login.tsx b/sasjs-tests/src/Login.tsx
index 38f05f2..cd0fac4 100644
--- a/sasjs-tests/src/Login.tsx
+++ b/sasjs-tests/src/Login.tsx
@@ -1,6 +1,6 @@
import React, { ReactElement, useState, useCallback, useContext } from "react";
import "./Login.scss";
-import { AppContext } from "./context/AppContext";
+import { AppContext } from "@sasjs/test-framework";
import { Redirect } from "react-router-dom";
const Login = (): ReactElement<{}> => {
diff --git a/sasjs-tests/src/PrivateRoute.tsx b/sasjs-tests/src/PrivateRoute.tsx
index a70d282..8420955 100644
--- a/sasjs-tests/src/PrivateRoute.tsx
+++ b/sasjs-tests/src/PrivateRoute.tsx
@@ -1,6 +1,6 @@
import React, { ReactElement, useContext, FunctionComponent } from "react";
import { Redirect, Route } from "react-router-dom";
-import { AppContext } from "./context/AppContext";
+import { AppContext } from "@sasjs/test-framework";
interface PrivateRouteProps {
component: FunctionComponent;
diff --git a/sasjs-tests/src/TestSuiteRunner.scss b/sasjs-tests/src/TestSuiteRunner.scss
deleted file mode 100644
index 880b9db..0000000
--- a/sasjs-tests/src/TestSuiteRunner.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-.button-container {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 16px;
-
- .loading-spinner {
- margin: 0 8px;
- }
-
- .submit-button {
- padding: 10px;
- min-height: 80px;
- font-size: 2em;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-}
diff --git a/sasjs-tests/src/TestSuiteRunner.tsx b/sasjs-tests/src/TestSuiteRunner.tsx
deleted file mode 100644
index 449371b..0000000
--- a/sasjs-tests/src/TestSuiteRunner.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import React, { useEffect, useState, ReactElement, useContext } from "react";
-import TestSuiteComponent from "./components/TestSuite";
-import TestSuiteCard from "./components/TestSuiteCard";
-import { TestSuite, Test } from "./types";
-import { basicTests } from "./testSuites/Basic";
-import "./TestSuiteRunner.scss";
-import SASjs from "@sasjs/adapter";
-import { AppContext } from "./context/AppContext";
-import { sendArrTests, sendObjTests } from "./testSuites/RequestData";
-import { specialCaseTests } from "./testSuites/SpecialCases";
-import { sasjsRequestTests } from "./testSuites/SasjsRequests";
-
-interface TestSuiteRunnerProps {
- adapter: SASjs;
-}
-const TestSuiteRunner = (
- props: TestSuiteRunnerProps
-): ReactElement => {
- const { adapter } = props;
- const { config } = useContext(AppContext);
- const [testSuites, setTestSuites] = useState([]);
- const [runTests, setRunTests] = useState(false);
- const [completedTestSuites, setCompletedTestSuites] = useState<
- {
- name: string;
- completedTests: {
- test: Test;
- result: boolean;
- error: Error | null;
- executionTime: number;
- }[];
- }[]
- >([]);
- const [currentTestSuite, setCurrentTestSuite] = useState(
- (null as unknown) as TestSuite
- );
-
- useEffect(() => {
- if (adapter) {
- setTestSuites([
- basicTests(adapter, config.userName, config.password),
- sendArrTests(adapter),
- sendObjTests(adapter),
- specialCaseTests(adapter),
- sasjsRequestTests(adapter),
- ]);
- setCompletedTestSuites([]);
- }
- }, [adapter]);
-
- useEffect(() => {
- if (testSuites.length) {
- setCurrentTestSuite(testSuites[0]);
- }
- }, [testSuites]);
-
- useEffect(() => {
- if (runTests) {
- setCompletedTestSuites([]);
- setCurrentTestSuite(testSuites[0]);
- }
- }, [runTests, testSuites]);
-
- return (
- <>
-
-
setRunTests(true)}
- disabled={runTests}
- >
- {runTests ? (
- <>
-
Running tests...
- >
- ) : (
- "Run tests!"
- )}
-
-
- {completedTestSuites.map((completedTestSuite, index) => {
- return (
-
- );
- })}
- {currentTestSuite && runTests && (
- {
- const currentIndex = testSuites.indexOf(currentTestSuite);
- const nextIndex =
- currentIndex < testSuites.length - 1 ? currentIndex + 1 : -1;
- if (nextIndex >= 0) {
- setCurrentTestSuite(testSuites[nextIndex]);
- } else {
- setCurrentTestSuite(null);
- }
- const newCompletedTestSuites = [
- ...completedTestSuites,
- { name, completedTests },
- ];
- setCompletedTestSuites(newCompletedTestSuites);
-
- if (newCompletedTestSuites.length === testSuites.length) {
- setRunTests(false);
- }
- }}
- />
- )}
- >
- );
-};
-
-export default TestSuiteRunner;
diff --git a/sasjs-tests/src/components/Test.tsx b/sasjs-tests/src/components/Test.tsx
deleted file mode 100644
index 6d13850..0000000
--- a/sasjs-tests/src/components/Test.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import React, { ReactElement, useEffect, useState } from "react";
-import TestCard from "./TestCard";
-import { start } from "repl";
-
-interface TestProps {
- title: string;
- description: string;
- beforeTest?: (...args: any) => Promise;
- afterTest?: (...args: any) => Promise;
- test: (context: any) => Promise;
- assertion: (...args: any) => boolean;
- onCompleted: (payload: {
- result: boolean;
- error: Error | null;
- executionTime: number;
- }) => void;
- context: any;
-}
-
-const getStatus = (isRunning: boolean, isPassed: boolean): string => {
- return isRunning ? "running" : isPassed ? "passed" : "failed";
-};
-
-const Test = (props: TestProps): ReactElement => {
- const {
- title,
- description,
- test,
- beforeTest,
- afterTest,
- assertion,
- onCompleted,
- context,
- } = props;
- const beforeTestFunction = beforeTest ? beforeTest : () => Promise.resolve();
- const afterTestFunction = afterTest ? afterTest : () => Promise.resolve();
- const [isRunning, setIsRunning] = useState(false);
- const [isPassed, setIsPassed] = useState(false);
-
- useEffect(() => {
- if (test && assertion) {
- const startTime = new Date().valueOf();
- setIsRunning(true);
- setIsPassed(false);
- beforeTestFunction()
- .then(() => test(context))
- .then((res) => {
- setIsRunning(false);
- setIsPassed(assertion(res, context));
- return Promise.resolve(assertion(res, context));
- })
- .then((testResult) => {
- afterTestFunction();
- const endTime = new Date().valueOf();
- const executionTime = (endTime - startTime) / 1000;
- onCompleted({ result: testResult, error: null, executionTime });
- })
- .catch((e) => {
- setIsRunning(false);
- setIsPassed(false);
- console.error(e);
- const endTime = new Date().valueOf();
- const executionTime = (endTime - startTime) / 1000;
- onCompleted({ result: false, error: e, executionTime });
- });
- }
- }, [test, assertion]);
-
- return (
-
- );
-};
-
-export default Test;
diff --git a/sasjs-tests/src/components/TestCard.scss b/sasjs-tests/src/components/TestCard.scss
deleted file mode 100644
index 306d078..0000000
--- a/sasjs-tests/src/components/TestCard.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-.test {
- display: inline-flex;
- padding: 8px;
- margin: 8px;
- flex-direction: column;
- border: 1px solid #ddd;
- border-radius: 5px;
- width: 20%;
-
- .title {
- font-weight: bold;
- color: #eee;
- font-size: 1em;
- }
-
- .description,
- .execution-time {
- color: #c6c0c0;
- padding: 8px 0;
- font-size: 0.8em;
- }
-
- .description {
- min-height: 50px;
- }
-
- .execution-time {
- color: #f9e804;
- }
-
- .icon {
- border-radius: 50%;
- width: 12px;
- height: 12px;
- margin-right: 8px;
- display: inline-block;
-
- &.running {
- background-color: yellow;
- }
-
- &.passed {
- background-color: green;
- }
-
- &.failed {
- background-color: red;
- }
- }
-}
-
-@media only screen and (max-width: 900px) {
- .test {
- width: 90%;
- }
-}
-
-@media only screen and (min-width: 901px) and (max-width: 1280px) {
- .test {
- width: 30%;
- }
-}
diff --git a/sasjs-tests/src/components/TestCard.tsx b/sasjs-tests/src/components/TestCard.tsx
deleted file mode 100644
index 404f39c..0000000
--- a/sasjs-tests/src/components/TestCard.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React, { ReactElement } from "react";
-import "./TestCard.scss";
-
-interface TestCardProps {
- title: string;
- description: string;
- status: string;
- error: Error | null;
- executionTime?: number;
-}
-const TestCard = (props: TestCardProps): ReactElement => {
- const { title, description, status, error, executionTime } = props;
-
- return (
-
-
{title}
-
{description}
-
- {executionTime ? executionTime.toFixed(2) + "s" : ""}
-
- {status === "running" && (
-
- Running...
-
- )}
- {status === "passed" && (
-
- Passed
-
- )}
- {status === "failed" && (
- <>
-
- Failed
-
- {!!error &&
{error.message}}
- >
- )}
-
- );
-};
-
-export default TestCard;
diff --git a/sasjs-tests/src/components/TestSuite.tsx b/sasjs-tests/src/components/TestSuite.tsx
deleted file mode 100644
index 850d141..0000000
--- a/sasjs-tests/src/components/TestSuite.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import React, { ReactElement, useState, useEffect } from "react";
-import "./TestSuiteCard.scss";
-import { Test } from "../types";
-import TestComponent from "./Test";
-import TestCard from "./TestCard";
-
-interface TestSuiteProps {
- name: string;
- tests: Test[];
- beforeAll?: (...args: any) => Promise;
- afterAll?: (...args: any) => Promise;
- onCompleted: (
- name: string,
- completedTests: {
- test: Test;
- result: boolean;
- error: Error | null;
- executionTime: number;
- }[]
- ) => void;
-}
-const TestSuite = (props: TestSuiteProps): ReactElement => {
- const { name, tests, beforeAll, afterAll, onCompleted } = props;
- const [context, setContext] = useState(null);
- const [completedTests, setCompletedTests] = useState<
- {
- test: Test;
- result: boolean;
- error: Error | null;
- executionTime: number;
- }[]
- >([]);
- const [currentTest, setCurrentTest] = useState(
- (null as unknown) as Test
- );
-
- useEffect(() => {
- if (beforeAll) {
- beforeAll().then((data) => setContext({ data }));
- }
- }, [beforeAll]);
-
- useEffect(() => {
- if (tests.length) {
- setCurrentTest(tests[0]);
- }
- setCompletedTests([]);
- setContext(null);
- }, [tests]);
-
- return (!!beforeAll && !!context) || !beforeAll ? (
-
-
{name}
- {currentTest && (
-
{
- const newCompleteTests = [
- ...completedTests,
- {
- test: currentTest,
- result: completedTest.result,
- error: completedTest.error,
- executionTime: completedTest.executionTime,
- },
- ];
- setCompletedTests(newCompleteTests);
- const currentIndex = tests.indexOf(currentTest);
- const nextIndex =
- currentIndex < tests.length - 1 ? currentIndex + 1 : -1;
- if (nextIndex >= 0) {
- setCurrentTest(tests[nextIndex]);
- } else {
- setCurrentTest(null);
- }
- if (newCompleteTests.length === tests.length) {
- if (afterAll) {
- afterAll().then(() => onCompleted(name, newCompleteTests));
- } else {
- onCompleted(name, newCompleteTests);
- }
- }
- }}
- />
- )}
- {completedTests.map((completedTest, index) => {
- const { test, result, error } = completedTest;
- const { title, description } = test;
- return (
-
- );
- })}
-
- ) : (
- <>>
- );
-};
-
-export default TestSuite;
diff --git a/sasjs-tests/src/components/TestSuiteCard.scss b/sasjs-tests/src/components/TestSuiteCard.scss
deleted file mode 100644
index 9ce7289..0000000
--- a/sasjs-tests/src/components/TestSuiteCard.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-.test-suite {
- .test-suite-name {
- font-size: 1.5em;
- font-weight: bold;
- color: #1f2027;
-
- &.passed {
- color: green;
- }
-
- &.failed {
- color: red;
- }
-
- &.running {
- color: yellow;
- }
- }
-}
diff --git a/sasjs-tests/src/components/TestSuiteCard.tsx b/sasjs-tests/src/components/TestSuiteCard.tsx
deleted file mode 100644
index c573e03..0000000
--- a/sasjs-tests/src/components/TestSuiteCard.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, { ReactElement } from "react";
-import "./TestSuiteCard.scss";
-import { Test } from "../types";
-import TestCard from "./TestCard";
-
-interface TestSuiteCardProps {
- name: string;
- tests: {
- test: Test;
- result: boolean;
- error: Error | null;
- executionTime: number;
- }[];
-}
-const TestSuiteCard = (
- props: TestSuiteCardProps
-): ReactElement => {
- const { name, tests } = props;
- const overallStatus = tests.map((t) => t.result).reduce((x, y) => x && y);
-
- return (
-
-
- {name}
-
- {tests.map((completedTest, index) => {
- const { test, result, error, executionTime } = completedTest;
- const { title, description } = test;
- return (
-
- );
- })}
-
- );
-};
-
-export default TestSuiteCard;
diff --git a/sasjs-tests/src/context/AppContext.tsx b/sasjs-tests/src/context/AppContext.tsx
deleted file mode 100644
index 6a20af3..0000000
--- a/sasjs-tests/src/context/AppContext.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React, { createContext, useState, useEffect, ReactNode } from "react";
-import SASjs from "@sasjs/adapter";
-
-export const AppContext = createContext<{
- config: any;
- sasJsConfig: any;
- isLoggedIn: boolean;
- setIsLoggedIn: (value: boolean) => void;
- adapter: SASjs;
-}>({
- config: null,
- sasJsConfig: null,
- isLoggedIn: false,
- setIsLoggedIn: (null as unknown) as (value: boolean) => void,
- adapter: (null as unknown) as SASjs,
-});
-
-export const AppProvider = (props: { children: ReactNode }) => {
- const [config, setConfig] = useState<{ sasJsConfig: any }>({
- sasJsConfig: null,
- });
-
- const [adapter, setAdapter] = useState((null as unknown) as SASjs);
-
- const [isLoggedIn, setIsLoggedIn] = useState(false);
-
- useEffect(() => {
- fetch("config.json")
- .then((res) => res.json())
- .then((configJson: any) => {
- setConfig(configJson);
- const sasjs = new SASjs(configJson.sasJsConfig);
- setAdapter(sasjs);
- sasjs.checkSession().then((response) => {
- setIsLoggedIn(response.isLoggedIn);
- });
- });
- }, []);
-
- return (
-
- {props.children}
-
- );
-};
diff --git a/sasjs-tests/src/index.tsx b/sasjs-tests/src/index.tsx
index 48791c8..effc537 100644
--- a/sasjs-tests/src/index.tsx
+++ b/sasjs-tests/src/index.tsx
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom";
import { Route, HashRouter, Switch } from "react-router-dom";
import "./index.scss";
import * as serviceWorker from "./serviceWorker";
-import { AppProvider } from "./context/AppContext";
+import { AppProvider } from "@sasjs/test-framework";
import PrivateRoute from "./PrivateRoute";
import Login from "./Login";
import App from "./App";
diff --git a/sasjs-tests/src/testSuites/Basic.ts b/sasjs-tests/src/testSuites/Basic.ts
index ee0be3d..903f11b 100644
--- a/sasjs-tests/src/testSuites/Basic.ts
+++ b/sasjs-tests/src/testSuites/Basic.ts
@@ -1,5 +1,5 @@
import SASjs, { ServerType, SASjsConfig } from "@sasjs/adapter";
-import { TestSuite } from "../types";
+import { TestSuite } from "@sasjs/test-framework";
const defaultConfig: SASjsConfig = {
serverUrl: window.location.origin,
diff --git a/sasjs-tests/src/testSuites/RequestData.ts b/sasjs-tests/src/testSuites/RequestData.ts
index 2ed3672..0f4b82d 100644
--- a/sasjs-tests/src/testSuites/RequestData.ts
+++ b/sasjs-tests/src/testSuites/RequestData.ts
@@ -1,5 +1,5 @@
import SASjs from "@sasjs/adapter";
-import { TestSuite } from "../types";
+import { TestSuite } from "@sasjs/test-framework";
const stringData: any = { table1: [{ col1: "first col value" }] };
const numericData: any = { table1: [{ col1: 3.14159265 }] };
diff --git a/sasjs-tests/src/testSuites/SasjsRequests.ts b/sasjs-tests/src/testSuites/SasjsRequests.ts
index c17f2d0..9a4a5e5 100644
--- a/sasjs-tests/src/testSuites/SasjsRequests.ts
+++ b/sasjs-tests/src/testSuites/SasjsRequests.ts
@@ -1,5 +1,5 @@
import SASjs from "@sasjs/adapter";
-import { TestSuite } from "../types";
+import { TestSuite } from "@sasjs/test-framework";
const data: any = { table1: [{ col1: "first col value" }] };
diff --git a/sasjs-tests/src/testSuites/SpecialCases.ts b/sasjs-tests/src/testSuites/SpecialCases.ts
index 6117805..0785204 100644
--- a/sasjs-tests/src/testSuites/SpecialCases.ts
+++ b/sasjs-tests/src/testSuites/SpecialCases.ts
@@ -1,5 +1,5 @@
import SASjs from "@sasjs/adapter";
-import { TestSuite } from "../types";
+import { TestSuite } from "@sasjs/test-framework";
const specialCharData: any = {
table1: [
diff --git a/sasjs-tests/src/types/index.ts b/sasjs-tests/src/types/index.ts
deleted file mode 100644
index 832aca1..0000000
--- a/sasjs-tests/src/types/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export interface Test {
- title: string;
- description: string;
- beforeTest?: (...args: any) => Promise;
- afterTest?: (...args: any) => Promise;
- test: (context?: any) => Promise;
- assertion: (...args: any) => boolean;
-}
-
-export interface TestSuite {
- name: string;
- tests: Test[];
- beforeAll?: (...args: any) => Promise;
- afterAll?: (...args: any) => Promise;
-}
diff --git a/sasjs-tests/src/utils/UploadFile.ts b/sasjs-tests/src/utils/UploadFile.ts
deleted file mode 100644
index 5dafccd..0000000
--- a/sasjs-tests/src/utils/UploadFile.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-export const uploadFile = (file: File, fileName: string, url: string) => {
- return new Promise((resolve, reject) => {
- const data = new FormData();
- data.append("file", file);
- data.append("filename", fileName);
- const xhr = new XMLHttpRequest();
- xhr.withCredentials = true;
- xhr.addEventListener("readystatechange", function () {
- if (this.readyState === 4) {
- let response: any;
- try {
- response = JSON.parse(this.responseText);
- } catch (e) {
- reject(e);
- }
- resolve(response);
- }
- });
- xhr.open("POST", url);
- xhr.setRequestHeader("cache-control", "no-cache");
- xhr.send(data);
- });
-};