1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-03 10:40:06 +00:00

Merge branch 'master' of https://github.com/sasjs/adapter into api-execution

This commit is contained in:
Krishna Acondy
2020-08-03 19:50:30 +01:00
29 changed files with 220 additions and 780 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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%;
}
}

View File

@@ -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<TestSuite[]>([]);
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 (
<div className="app">
<div className="controls">
<div className="row">
<label>Debug</label>
<div className="debug-toggle">
<label className="switch">
<input
type="checkbox"
onChange={(e) => setDebug(e.target.checked)}
/>
<span className="knob"></span>
</label>
</div>
</div>
<div className="row app-loc">
<label>App Loc</label>
<input
type="text"
className="app-loc-input"
value={appLoc}
onChange={(e) => setAppLoc(e.target.value)}
placeholder="AppLoc"
/>
</div>
</div>
{adapter && <TestSuiteRunner adapter={adapter} />}
{adapter && testSuites && <TestSuiteRunner testSuites={testSuites} />}
</div>
);
};

View File

@@ -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<{}> => {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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<TestSuiteRunnerProps> => {
const { adapter } = props;
const { config } = useContext(AppContext);
const [testSuites, setTestSuites] = useState<TestSuite[]>([]);
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<TestSuite | null>(
(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 (
<>
<div className="button-container">
<button
className={runTests ? "submit-button disabled" : "submit-button"}
onClick={() => setRunTests(true)}
disabled={runTests}
>
{runTests ? (
<>
<div className="loading-spinner"></div>Running tests...
</>
) : (
"Run tests!"
)}
</button>
</div>
{completedTestSuites.map((completedTestSuite, index) => {
return (
<TestSuiteCard
key={index}
tests={completedTestSuite.completedTests}
name={completedTestSuite.name}
/>
);
})}
{currentTestSuite && runTests && (
<TestSuiteComponent
{...currentTestSuite}
onCompleted={(
name,
completedTests: {
test: Test;
result: boolean;
error: Error | null;
executionTime: number;
}[]
) => {
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;

View File

@@ -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<any>;
afterTest?: (...args: any) => Promise<any>;
test: (context: any) => Promise<any>;
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<TestProps> => {
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 (
<TestCard
title={title}
description={description}
status={getStatus(isRunning, isPassed)}
error={null}
/>
);
};
export default Test;

View File

@@ -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%;
}
}

View File

@@ -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<TestCardProps> => {
const { title, description, status, error, executionTime } = props;
return (
<div className="test">
<code className="title">{title}</code>
<span className="description">{description}</span>
<span className="execution-time">
{executionTime ? executionTime.toFixed(2) + "s" : ""}
</span>
{status === "running" && (
<div>
<span className="icon running"></span>Running...
</div>
)}
{status === "passed" && (
<div>
<span className="icon passed"></span>Passed
</div>
)}
{status === "failed" && (
<>
<div>
<span className="icon failed"></span>Failed
</div>
{!!error && <code>{error.message}</code>}
</>
)}
</div>
);
};
export default TestCard;

View File

@@ -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<any>;
afterAll?: (...args: any) => Promise<any>;
onCompleted: (
name: string,
completedTests: {
test: Test;
result: boolean;
error: Error | null;
executionTime: number;
}[]
) => void;
}
const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
const { name, tests, beforeAll, afterAll, onCompleted } = props;
const [context, setContext] = useState<any>(null);
const [completedTests, setCompletedTests] = useState<
{
test: Test;
result: boolean;
error: Error | null;
executionTime: number;
}[]
>([]);
const [currentTest, setCurrentTest] = useState<Test | null>(
(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 ? (
<div className="test-suite">
<div className="test-suite-name running">{name}</div>
{currentTest && (
<TestComponent
{...currentTest}
context={context}
onCompleted={(completedTest) => {
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 (
<TestCard
key={index}
title={title}
description={description}
status={result === true ? "passed" : "failed"}
error={error}
/>
);
})}
</div>
) : (
<></>
);
};
export default TestSuite;

View File

@@ -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;
}
}
}

View File

@@ -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<TestSuiteCardProps> => {
const { name, tests } = props;
const overallStatus = tests.map((t) => t.result).reduce((x, y) => x && y);
return (
<div className="test-suite">
<div className={`test-suite-name ${overallStatus ? "passed" : "failed"}`}>
{name}
</div>
{tests.map((completedTest, index) => {
const { test, result, error, executionTime } = completedTest;
const { title, description } = test;
return (
<TestCard
key={index}
title={title}
description={description}
status={result === true ? "passed" : "failed"}
error={error}
executionTime={executionTime}
/>
);
})}
</div>
);
};
export default TestSuiteCard;

View File

@@ -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<SASjs>((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 (
<AppContext.Provider
value={{
config,
sasJsConfig: config.sasJsConfig,
isLoggedIn,
setIsLoggedIn,
adapter,
}}
>
{props.children}
</AppContext.Provider>
);
};

View File

@@ -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";

View File

@@ -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,

View File

@@ -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 }] };

View File

@@ -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" }] };

View File

@@ -1,5 +1,5 @@
import SASjs from "@sasjs/adapter";
import { TestSuite } from "../types";
import { TestSuite } from "@sasjs/test-framework";
const specialCharData: any = {
table1: [

View File

@@ -1,15 +0,0 @@
export interface Test {
title: string;
description: string;
beforeTest?: (...args: any) => Promise<any>;
afterTest?: (...args: any) => Promise<any>;
test: (context?: any) => Promise<any>;
assertion: (...args: any) => boolean;
}
export interface TestSuite {
name: string;
tests: Test[];
beforeAll?: (...args: any) => Promise<any>;
afterAll?: (...args: any) => Promise<any>;
}

View File

@@ -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);
});
};